@ciq-dev/neoiq-foundation-node 1.0.0 → 1.0.1-beta.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.
- package/README.md +228 -68
- package/dist/index.d.mts +573 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +570 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +885 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +832 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +27 -21
- package/dist/http-client.d.ts +0 -80
- package/dist/http-client.d.ts.map +0 -1
- package/dist/http-client.js +0 -188
- package/dist/http-client.js.map +0 -1
- package/dist/observability.d.ts +0 -132
- package/dist/observability.d.ts.map +0 -1
- package/dist/observability.js +0 -246
- package/dist/observability.js.map +0 -1
- package/dist/plugin.d.ts +0 -40
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -176
- package/dist/plugin.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["input: FoundationConfigInput","context: RequestContext","fn: () => T","key: K","updates: Partial<RequestContext>","options: LoggerOptions","pinoLogger: PinoLogger","serviceName: string","level: string","obj: object","msg?: string","fallback: Logger","globalLogger: Logger | null","logger: Logger","sdk: NodeSDK | null","options: TracingOptions","config: AutoInstrumentationConfig","mapping: Record<string, string>","result: Record<string, { enabled: boolean }>","name: string","meterProvider: MeterProvider | null","options: MetricsOptions","name: string","version: string","headers: Record<string, unknown>","redactList: string[]","redacted: Record<string, unknown>","body: unknown","maxSize: number","options: FastifyPluginOptions","plugin: FastifyPluginAsync<Partial<FastifyPluginOptions>>","ctx: PluginRequestContext","fn: () => T","_ctx: PluginRequestContext","requestCounter: ReturnType<ReturnType<typeof getMeter>['createCounter']>","requestDuration: ReturnType<ReturnType<typeof getMeter>['createHistogram']>","requestErrors: ReturnType<ReturnType<typeof getMeter>['createCounter']>","request: FastifyRequest","reply: FastifyReply","span: Span | undefined","requestContext: PluginRequestContext","logData: Record<string, unknown>","_reply: FastifyReply","error: Error","options: HttpClientOptions","logger: Logger","requestCounter: ReturnType<ReturnType<typeof getMeter>['createCounter']> | null","requestDuration: ReturnType<ReturnType<typeof getMeter>['createHistogram']> | null","requestErrors: ReturnType<ReturnType<typeof getMeter>['createCounter']> | null","config: InternalAxiosRequestConfig","carrier: Record<string, string>","response: AxiosResponse","retryConfig: IAxiosRetryConfig","config: AxiosRequestConfig","input: FoundationConfigInput","logger: Logger","loggingError: string | undefined","tracerInstance: Tracer | null","tracingError: string | undefined","meterInstance: Meter | null","metricsError: string | undefined","foundation: FoundationInstance","name?: string","name: string","version?: string","promises: Promise<void>[]","fn: () => T | Promise<T>","fallback?: T"],"sources":["../src/config.ts","../src/features/context.ts","../src/features/logging.ts","../src/features/tracing.ts","../src/features/metrics.ts","../src/integrations/fastify-plugin.ts","../src/integrations/http-client.ts","../src/foundation.ts"],"sourcesContent":["/**\n * Foundation Configuration with Zod Validation\n */\n\nimport { z } from 'zod';\n\n// Auto-Instrumentation Config\nexport const AutoInstrumentationConfigSchema = z\n .object({\n http: z.boolean().default(true),\n fastify: z.boolean().default(true),\n express: z.boolean().default(true),\n mongodb: z.boolean().default(true),\n pg: z.boolean().default(true),\n mysql: z.boolean().default(true),\n redis: z.boolean().default(true),\n ioredis: z.boolean().default(true),\n grpc: z.boolean().default(true),\n fs: z.boolean().default(false),\n dns: z.boolean().default(false),\n })\n .partial();\n\nexport type AutoInstrumentationConfig = z.infer<typeof AutoInstrumentationConfigSchema>;\n\n// Features Config\nexport const FeaturesConfigSchema = z\n .object({\n tracing: z.boolean().default(true),\n metrics: z.boolean().default(true),\n logging: z.boolean().default(true),\n autoInstrumentation: AutoInstrumentationConfigSchema.default({}),\n })\n .partial();\n\nexport type FeaturesConfig = z.infer<typeof FeaturesConfigSchema>;\n\n// OTEL Config\nconst DEFAULT_OTEL_ENDPOINT =\n 'http://otel-stack-deployment-collector.observability.svc.cluster.local:4317';\n\nexport const OtelConfigSchema = z\n .object({\n endpoint: z.string().default(DEFAULT_OTEL_ENDPOINT),\n metricsIntervalMs: z.number().min(1000).default(5000),\n traceSampleRate: z.number().min(0).max(1).default(1.0),\n })\n .partial();\n\nexport type OtelConfig = z.infer<typeof OtelConfigSchema>;\n\n// Logging Config\nexport const LoggingConfigSchema = z\n .object({\n level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n prettyPrint: z.boolean().optional(),\n })\n .partial();\n\nexport type LoggingConfig = z.infer<typeof LoggingConfigSchema>;\n\n// Request Logging Config\nexport const RequestLoggingConfigSchema = z\n .object({\n logHeaders: z.boolean().default(true),\n logBody: z.boolean().default(false),\n logResponseBody: z.boolean().default(false),\n maxBodySize: z.number().default(10 * 1024),\n redactHeaders: z.array(z.string()).optional(),\n })\n .partial();\n\nexport type RequestLoggingConfig = z.infer<typeof RequestLoggingConfigSchema>;\n\n// Main Foundation Config\nexport const FoundationConfigSchema = z.object({\n serviceName: z.string().min(1, 'serviceName is required'),\n serviceVersion: z.string().default(process.env.SERVICE_VERSION || '1.0.0'),\n environment: z\n .enum(['development', 'staging', 'qa', 'production'])\n .default((process.env.NODE_ENV as 'development' | 'production') || 'development'),\n features: FeaturesConfigSchema.default({}),\n otel: OtelConfigSchema.default({}),\n logging: LoggingConfigSchema.default({}),\n requestLogging: RequestLoggingConfigSchema.default({}),\n});\n\nexport type FoundationConfig = z.infer<typeof FoundationConfigSchema>;\n\nexport type FoundationConfigInput = Partial<FoundationConfig> & {\n serviceName: string;\n};\n\n/** Parse and validate configuration */\nexport function parseConfig(input: FoundationConfigInput): FoundationConfig {\n const result = FoundationConfigSchema.safeParse(input);\n\n if (!result.success) {\n const errors = result.error.errors\n .map((e) => ` - ${e.path.join('.')}: ${e.message}`)\n .join('\\n');\n throw new Error(`Invalid foundation configuration:\\n${errors}`);\n }\n\n return result.data;\n}\n\n/** Get default OTEL endpoint */\nexport function getDefaultOtelEndpoint(): string {\n return process.env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_OTEL_ENDPOINT;\n}\n","/**\n * Context Manager - AsyncLocalStorage for request context propagation\n */\n\nimport { AsyncLocalStorage } from 'async_hooks';\n\nexport interface RequestContext {\n correlationId?: string;\n traceId?: string;\n spanId?: string;\n startTime?: number;\n [key: string]: unknown;\n}\n\nexport interface ContextManager {\n getContext(): RequestContext | undefined;\n run<T>(context: RequestContext, fn: () => T): T;\n get<K extends keyof RequestContext>(key: K): RequestContext[K] | undefined;\n update(updates: Partial<RequestContext>): RequestContext | undefined;\n}\n\n/** Create a new context manager instance */\nexport function createContextManager(): ContextManager {\n const als = new AsyncLocalStorage<RequestContext>();\n\n return {\n getContext: () => als.getStore(),\n\n run<T>(context: RequestContext, fn: () => T): T {\n return als.run(context, fn);\n },\n\n get<K extends keyof RequestContext>(key: K): RequestContext[K] | undefined {\n return als.getStore()?.[key];\n },\n\n update(updates: Partial<RequestContext>): RequestContext | undefined {\n const current = als.getStore();\n if (!current) return undefined;\n Object.assign(current, updates);\n return current;\n },\n };\n}\n","/**\n * Structured Logging with Pino\n */\n\nimport pino, { Logger as PinoLogger } from 'pino';\nimport { trace } from '@opentelemetry/api';\nimport { type ContextManager } from './context';\n\nexport interface LoggerOptions {\n serviceName: string;\n serviceVersion: string;\n environment: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n prettyPrint: boolean;\n contextManager?: ContextManager;\n}\n\nexport interface Logger {\n debug: (obj: object, msg?: string) => void;\n info: (obj: object, msg?: string) => void;\n warn: (obj: object, msg?: string) => void;\n error: (obj: object, msg?: string) => void;\n child: (bindings: object) => Logger;\n readonly pino: PinoLogger;\n}\n\n/** Create a structured logger with automatic trace context injection */\nexport function createLogger(options: LoggerOptions): Logger {\n const { serviceName, serviceVersion, environment, level, prettyPrint, contextManager } = options;\n\n const pinoLogger = pino({\n level,\n base: { service: serviceName, version: serviceVersion, env: environment },\n mixin: () => {\n const span = trace.getActiveSpan();\n const spanContext = span?.spanContext();\n const ctx = contextManager?.getContext();\n\n return {\n traceId: spanContext?.traceId || ctx?.traceId,\n spanId: spanContext?.spanId || ctx?.spanId,\n correlationId: ctx?.correlationId,\n };\n },\n formatters: { level: (label) => ({ level: label }) },\n transport: prettyPrint\n ? {\n target: 'pino-pretty',\n options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' },\n }\n : undefined,\n });\n\n return wrapPinoLogger(pinoLogger);\n}\n\nfunction wrapPinoLogger(pinoLogger: PinoLogger): Logger {\n return {\n debug: (obj, msg) => pinoLogger.debug(obj, msg),\n info: (obj, msg) => pinoLogger.info(obj, msg),\n warn: (obj, msg) => pinoLogger.warn(obj, msg),\n error: (obj, msg) => pinoLogger.error(obj, msg),\n child: (bindings) => wrapPinoLogger(pinoLogger.child(bindings)),\n pino: pinoLogger,\n };\n}\n\n/** Fallback logger when Pino is not available */\nexport function createFallbackLogger(serviceName: string = 'unknown'): Logger {\n const log = (level: string, obj: object, msg?: string) => {\n const logObj = {\n timestamp: new Date().toISOString(),\n level,\n service: serviceName,\n ...obj,\n msg,\n };\n console[level === 'debug' ? 'log' : (level as 'info' | 'warn' | 'error')](\n JSON.stringify(logObj)\n );\n };\n\n const fallback: Logger = {\n debug: (obj, msg) => log('debug', obj, msg),\n info: (obj, msg) => log('info', obj, msg),\n warn: (obj, msg) => log('warn', obj, msg),\n error: (obj, msg) => log('error', obj, msg),\n child: () => fallback,\n pino: null as unknown as PinoLogger,\n };\n\n return fallback;\n}\n\n// Global logger for standalone usage\nlet globalLogger: Logger | null = null;\n\nexport function setGlobalLogger(logger: Logger): void {\n globalLogger = logger;\n}\n\nexport function getGlobalLogger(): Logger {\n return globalLogger || createFallbackLogger();\n}\n","/**\n * OpenTelemetry Tracing Setup\n */\n\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { trace, context, propagation, SpanStatusCode, type Tracer } from '@opentelemetry/api';\nimport { type AutoInstrumentationConfig, getDefaultOtelEndpoint } from '../config';\n\nexport interface TracingOptions {\n serviceName: string;\n serviceVersion: string;\n environment: string;\n endpoint?: string;\n sampleRate?: number;\n autoInstrumentation?: AutoInstrumentationConfig;\n}\n\nlet sdk: NodeSDK | null = null;\nlet isInitialized = false;\n\n/** Initialize OpenTelemetry tracing */\nexport function setupTracing(options: TracingOptions): void {\n if (isInitialized) {\n console.warn('[neoiq-foundation] Tracing already initialized');\n return;\n }\n\n const {\n serviceName,\n serviceVersion,\n environment,\n endpoint = getDefaultOtelEndpoint(),\n autoInstrumentation = {},\n } = options;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: serviceVersion,\n 'deployment.environment': environment,\n });\n\n const traceExporter = new OTLPTraceExporter({ url: endpoint });\n const instrumentationConfig = buildInstrumentationConfig(autoInstrumentation);\n\n sdk = new NodeSDK({\n resource,\n traceExporter,\n instrumentations: [getNodeAutoInstrumentations(instrumentationConfig)],\n });\n\n sdk.start();\n isInitialized = true;\n}\n\nfunction buildInstrumentationConfig(\n config: AutoInstrumentationConfig\n): Record<string, { enabled: boolean }> {\n const mapping: Record<string, string> = {\n http: '@opentelemetry/instrumentation-http',\n fastify: '@opentelemetry/instrumentation-fastify',\n express: '@opentelemetry/instrumentation-express',\n mongodb: '@opentelemetry/instrumentation-mongodb',\n pg: '@opentelemetry/instrumentation-pg',\n mysql: '@opentelemetry/instrumentation-mysql',\n redis: '@opentelemetry/instrumentation-redis',\n ioredis: '@opentelemetry/instrumentation-ioredis',\n grpc: '@opentelemetry/instrumentation-grpc',\n fs: '@opentelemetry/instrumentation-fs',\n dns: '@opentelemetry/instrumentation-dns',\n };\n\n const result: Record<string, { enabled: boolean }> = {};\n for (const [key, instrumentationName] of Object.entries(mapping)) {\n const userSetting = config[key as keyof AutoInstrumentationConfig];\n const defaultValue = key !== 'fs' && key !== 'dns';\n result[instrumentationName] = { enabled: userSetting ?? defaultValue };\n }\n return result;\n}\n\n/** Shutdown tracing gracefully */\nexport async function shutdownTracing(): Promise<void> {\n if (!sdk) return;\n try {\n await sdk.shutdown();\n isInitialized = false;\n sdk = null;\n } catch (error) {\n console.error('[neoiq-foundation] Error shutting down tracing:', error);\n }\n}\n\nexport function getTracer(name: string): Tracer {\n return trace.getTracer(name);\n}\n\nexport function getActiveSpan() {\n return trace.getActiveSpan();\n}\n\nexport function getTraceContext(): { traceId?: string; spanId?: string } {\n const span = trace.getActiveSpan();\n if (!span) return {};\n const ctx = span.spanContext();\n return { traceId: ctx.traceId, spanId: ctx.spanId };\n}\n\nexport function isTracingEnabled(): boolean {\n return isInitialized;\n}\n\nexport { trace, context, propagation, SpanStatusCode };\nexport type { Tracer };\n","/**\n * OpenTelemetry Metrics Setup\n */\n\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { metrics, type Meter } from '@opentelemetry/api';\nimport { getDefaultOtelEndpoint } from '../config';\n\nexport interface MetricsOptions {\n serviceName: string;\n serviceVersion: string;\n environment: string;\n endpoint?: string;\n intervalMs?: number;\n}\n\nlet meterProvider: MeterProvider | null = null;\nlet isInitialized = false;\n\n/** Initialize OpenTelemetry metrics */\nexport function setupMetrics(options: MetricsOptions): void {\n if (isInitialized) {\n console.warn('[neoiq-foundation] Metrics already initialized');\n return;\n }\n\n const {\n serviceName,\n serviceVersion,\n environment,\n endpoint = getDefaultOtelEndpoint(),\n intervalMs = 5000,\n } = options;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: serviceVersion,\n 'deployment.environment': environment,\n });\n\n const metricExporter = new OTLPMetricExporter({ url: endpoint });\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: intervalMs,\n });\n\n meterProvider = new MeterProvider({ resource, readers: [metricReader] });\n metrics.setGlobalMeterProvider(meterProvider);\n isInitialized = true;\n}\n\n/** Shutdown metrics gracefully */\nexport async function shutdownMetrics(): Promise<void> {\n if (!meterProvider) return;\n try {\n await meterProvider.shutdown();\n isInitialized = false;\n meterProvider = null;\n } catch (error) {\n console.error('[neoiq-foundation] Error shutting down metrics:', error);\n }\n}\n\nexport function getMeter(name: string, version: string = '1.0.0'): Meter {\n return metrics.getMeter(name, version);\n}\n\nexport function isMetricsEnabled(): boolean {\n return isInitialized;\n}\n\nexport { metrics };\nexport type { Meter };\n","/**\n * Fastify Observability Plugin\n */\n\nimport { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';\nimport fp from 'fastify-plugin';\nimport { randomUUID } from 'crypto';\nimport { trace, context, propagation, SpanStatusCode, Span } from '@opentelemetry/api';\nimport { type ContextManager } from '../features/context';\nimport { type Logger, getGlobalLogger } from '../features/logging';\nimport { getMeter } from '../features/metrics';\n\ninterface PluginRequestContext {\n correlationId: string;\n traceId: string;\n spanId: string;\n startTime: number;\n [key: string]: unknown;\n}\n\nexport interface RequestLoggingOptions {\n logHeaders?: boolean;\n logBody?: boolean;\n logResponseBody?: boolean;\n maxBodySize?: number;\n redactHeaders?: string[];\n}\n\nexport interface FastifyPluginOptions {\n serviceName: string;\n logger?: Logger;\n contextManager?: ContextManager;\n tracingEnabled?: boolean;\n metricsEnabled?: boolean;\n excludeRoutes?: string[];\n /** Request/response logging options */\n requestLogging?: RequestLoggingOptions;\n}\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n __span?: Span;\n __requestContext?: PluginRequestContext;\n }\n}\n\n// Default headers to redact for security\nconst DEFAULT_REDACT_HEADERS = [\n 'authorization',\n 'cookie',\n 'x-api-key',\n 'x-auth-token',\n 'set-cookie',\n];\n\n/** Redact sensitive headers */\nfunction redactHeaders(\n headers: Record<string, unknown>,\n redactList: string[]\n): Record<string, unknown> {\n const redacted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (redactList.includes(key.toLowerCase())) {\n redacted[key] = '[REDACTED]';\n } else {\n redacted[key] = value;\n }\n }\n return redacted;\n}\n\n/** Truncate body if too large */\nfunction truncateBody(body: unknown, maxSize: number): unknown {\n if (body === undefined || body === null) return undefined;\n const str = typeof body === 'string' ? body : JSON.stringify(body);\n if (str.length > maxSize) {\n return `[TRUNCATED - ${str.length} bytes]`;\n }\n return body;\n}\n\n/** Create a configured Fastify observability plugin */\nexport function createObservabilityPlugin(options: FastifyPluginOptions) {\n const plugin: FastifyPluginAsync<Partial<FastifyPluginOptions>> = async (fastify, pluginOpts) => {\n const {\n serviceName,\n logger = getGlobalLogger(),\n contextManager,\n tracingEnabled = true,\n metricsEnabled = true,\n excludeRoutes = ['/health', '/health/', '/healthz', '/ready', '/live'],\n requestLogging = {},\n } = { ...options, ...pluginOpts };\n\n // Request logging config\n const logHeaders = requestLogging.logHeaders ?? true;\n const logBody = requestLogging.logBody ?? false;\n const logResponseBody = requestLogging.logResponseBody ?? false;\n const maxBodySize = requestLogging.maxBodySize ?? 10 * 1024; // 10KB\n const headersToRedact = requestLogging.redactHeaders ?? DEFAULT_REDACT_HEADERS;\n\n const runInContext = contextManager\n ? <T>(ctx: PluginRequestContext, fn: () => T) => contextManager.run(ctx, fn)\n : <T>(_ctx: PluginRequestContext, fn: () => T) => fn();\n\n const tracer = tracingEnabled ? trace.getTracer('neoiq-foundation-node') : null;\n\n let requestCounter: ReturnType<ReturnType<typeof getMeter>['createCounter']>;\n let requestDuration: ReturnType<ReturnType<typeof getMeter>['createHistogram']>;\n let requestErrors: ReturnType<ReturnType<typeof getMeter>['createCounter']>;\n\n if (metricsEnabled) {\n const meter = getMeter(serviceName);\n requestCounter = meter.createCounter('http.server.requests.total');\n requestDuration = meter.createHistogram('http.server.request.duration', { unit: 'ms' });\n requestErrors = meter.createCounter('http.server.requests.errors');\n }\n\n // onRequest - Extract context, start span, log headers\n fastify.addHook('onRequest', (request: FastifyRequest, reply: FastifyReply, done) => {\n if (excludeRoutes.some((route) => request.url.startsWith(route))) {\n done();\n return;\n }\n\n const correlationId = (request.headers['x-request-id'] as string) || randomUUID();\n reply.header('x-request-id', correlationId);\n\n let span: Span | undefined;\n let traceId = '';\n let spanId = '';\n\n if (tracer) {\n const parentContext = propagation.extract(context.active(), request.headers);\n span = tracer.startSpan(\n `${request.method} ${request.routeOptions?.url || request.url}`,\n {\n kind: 1,\n attributes: {\n 'http.method': request.method,\n 'http.url': request.url,\n 'http.route': request.routeOptions?.url || request.url,\n 'http.user_agent': request.headers['user-agent'] || '',\n 'http.correlation_id': correlationId,\n },\n },\n parentContext\n );\n const spanContext = span.spanContext();\n traceId = spanContext.traceId;\n spanId = spanContext.spanId;\n }\n\n const requestContext: PluginRequestContext = {\n correlationId,\n traceId,\n spanId,\n startTime: Date.now(),\n };\n request.__span = span;\n request.__requestContext = requestContext;\n\n runInContext(requestContext, () => {\n const logData: Record<string, unknown> = {\n correlationId,\n traceId: traceId || undefined,\n method: request.method,\n url: request.url,\n ip: request.ip,\n userAgent: request.headers['user-agent'],\n };\n\n // Log headers (redacted)\n if (logHeaders) {\n logData.headers = redactHeaders(\n request.headers as Record<string, unknown>,\n headersToRedact\n );\n }\n\n logger.info(logData, 'Request received');\n done();\n });\n });\n\n // preHandler - Log request body (if enabled)\n if (logBody) {\n fastify.addHook('preHandler', (request: FastifyRequest, _reply: FastifyReply, done) => {\n const ctx = request.__requestContext;\n if (!ctx || !request.body) {\n done();\n return;\n }\n\n runInContext(ctx, () => {\n logger.debug(\n {\n correlationId: ctx.correlationId,\n body: truncateBody(request.body, maxBodySize),\n },\n 'Request body'\n );\n done();\n });\n });\n }\n\n // onSend - Log response body (if enabled)\n if (logResponseBody) {\n fastify.addHook('onSend', (request: FastifyRequest, reply: FastifyReply, payload, done) => {\n const ctx = request.__requestContext;\n if (!ctx) {\n done(null, payload);\n return;\n }\n\n runInContext(ctx, () => {\n logger.debug(\n {\n correlationId: ctx.correlationId,\n statusCode: reply.statusCode,\n body: truncateBody(payload, maxBodySize),\n },\n 'Response body'\n );\n done(null, payload);\n });\n });\n }\n\n // onResponse - End span, record metrics\n fastify.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply, done) => {\n const ctx = request.__requestContext;\n const span = request.__span;\n if (!ctx) {\n done();\n return;\n }\n\n const durationMs = Date.now() - ctx.startTime;\n const route = request.routeOptions?.url || request.url;\n const labels = { method: request.method, route, status_code: String(reply.statusCode) };\n\n runInContext(ctx, () => {\n logger.info(\n {\n correlationId: ctx.correlationId,\n method: request.method,\n statusCode: reply.statusCode,\n durationMs,\n },\n 'Request completed'\n );\n\n if (metricsEnabled) {\n requestCounter.add(1, labels);\n requestDuration.record(durationMs, labels);\n if (reply.statusCode >= 400) requestErrors.add(1, labels);\n }\n\n if (span) {\n span.setStatus({\n code: reply.statusCode < 400 ? SpanStatusCode.OK : SpanStatusCode.ERROR,\n });\n span.setAttribute('http.status_code', reply.statusCode);\n span.setAttribute('http.response_time_ms', durationMs);\n span.end();\n }\n\n done();\n });\n });\n\n // onError - Record exception on span\n fastify.addHook(\n 'onError',\n (request: FastifyRequest, _reply: FastifyReply, error: Error, done) => {\n const ctx = request.__requestContext;\n const span = request.__span;\n if (!ctx) {\n done();\n return;\n }\n\n runInContext(ctx, () => {\n logger.error(\n {\n correlationId: ctx.correlationId,\n method: request.method,\n url: request.url,\n error: error.message,\n },\n 'Request failed'\n );\n\n if (span) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n }\n\n done();\n });\n }\n );\n };\n\n return fp(plugin, { name: 'neoiq-observability', fastify: '>=4.x' });\n}\n","/**\n * HTTP Client with Observability\n */\n\nimport axios from 'axios';\nimport type {\n AxiosInstance,\n AxiosRequestConfig,\n InternalAxiosRequestConfig,\n AxiosResponse,\n} from 'axios';\nimport axiosRetry, { IAxiosRetryConfig } from 'axios-retry';\nimport CircuitBreaker from 'opossum';\nimport { context, propagation } from '@opentelemetry/api';\nimport type { FoundationInstance } from '../foundation';\nimport { getGlobalLogger, type Logger } from '../features/logging';\nimport { getMeter } from '../features/metrics';\n\nexport interface HttpClientOptions {\n baseURL: string;\n serviceName: string;\n timeout?: number;\n retry?: {\n retries?: number;\n retryDelay?: number;\n retryStatusCodes?: number[];\n };\n circuitBreaker?: {\n enabled?: boolean;\n resetTimeout?: number;\n errorThresholdPercentage?: number;\n };\n headers?: Record<string, string>;\n foundation?: FoundationInstance;\n}\n\nexport type HttpClient = AxiosInstance;\n\n/** Create a configured HTTP client with full observability */\nexport function createHttpClient(options: HttpClientOptions): HttpClient {\n const {\n baseURL,\n serviceName,\n timeout = 30000,\n retry = {},\n circuitBreaker: cbOptions = {},\n headers = {},\n foundation,\n } = options;\n\n const logger: Logger = foundation?.logger || getGlobalLogger();\n const getContext = () => foundation?.context.getContext();\n const metricsEnabled = foundation?.features.metrics ?? true;\n const tracingEnabled = foundation?.features.tracing ?? true;\n\n const client = axios.create({\n baseURL,\n timeout,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n\n // Metrics\n let requestCounter: ReturnType<ReturnType<typeof getMeter>['createCounter']> | null = null;\n let requestDuration: ReturnType<ReturnType<typeof getMeter>['createHistogram']> | null = null;\n let requestErrors: ReturnType<ReturnType<typeof getMeter>['createCounter']> | null = null;\n\n if (metricsEnabled) {\n const meter = getMeter(`http-client-${serviceName}`);\n requestCounter = meter.createCounter('http.client.requests.total');\n requestDuration = meter.createHistogram('http.client.request.duration', { unit: 'ms' });\n requestErrors = meter.createCounter('http.client.requests.errors');\n }\n\n // Request interceptor - Add trace context\n client.interceptors.request.use((config: InternalAxiosRequestConfig) => {\n if (tracingEnabled) {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n if (carrier.traceparent) config.headers.set('traceparent', carrier.traceparent);\n if (carrier.tracestate) config.headers.set('tracestate', carrier.tracestate);\n }\n\n const reqCtx = getContext();\n if (reqCtx?.correlationId) config.headers.set('x-request-id', reqCtx.correlationId);\n\n (config as InternalAxiosRequestConfig & { __startTime: number }).__startTime = Date.now();\n\n logger.debug(\n {\n method: config.method?.toUpperCase(),\n url: `${config.baseURL || ''}${config.url}`,\n targetService: serviceName,\n },\n 'Outbound HTTP request'\n );\n\n return config;\n });\n\n // Response interceptor - Log and record metrics\n client.interceptors.response.use(\n (response: AxiosResponse) => {\n const config = response.config as InternalAxiosRequestConfig & { __startTime?: number };\n const durationMs = Date.now() - (config.__startTime || Date.now());\n const labels = {\n target_service: serviceName,\n method: config.method?.toUpperCase() || 'GET',\n status_code: String(response.status),\n };\n\n logger.debug(\n {\n method: config.method?.toUpperCase(),\n statusCode: response.status,\n durationMs,\n targetService: serviceName,\n },\n 'Outbound HTTP response'\n );\n\n if (metricsEnabled) {\n requestCounter?.add(1, labels);\n requestDuration?.record(durationMs, labels);\n }\n\n return response;\n },\n (error) => {\n const config = error.config as\n | (InternalAxiosRequestConfig & { __startTime?: number })\n | undefined;\n const durationMs = config ? Date.now() - (config.__startTime || Date.now()) : 0;\n const statusCode = error.response?.status || 0;\n const labels = {\n target_service: serviceName,\n method: config?.method?.toUpperCase() || 'GET',\n status_code: String(statusCode),\n };\n\n logger.error(\n {\n method: config?.method?.toUpperCase(),\n statusCode,\n durationMs,\n error: error.message,\n targetService: serviceName,\n },\n 'Outbound HTTP error'\n );\n\n if (metricsEnabled) {\n requestCounter?.add(1, labels);\n requestDuration?.record(durationMs, labels);\n requestErrors?.add(1, labels);\n }\n\n return Promise.reject(error);\n }\n );\n\n // Retry config\n const retryConfig: IAxiosRetryConfig = {\n retries: retry.retries ?? 3,\n retryDelay: (retryCount) => (retry.retryDelay ?? 1000) * Math.pow(2, retryCount - 1),\n retryCondition: (error) => {\n const retryStatusCodes = retry.retryStatusCodes ?? [408, 429, 500, 502, 503, 504];\n return !error.response || retryStatusCodes.includes(error.response?.status || 0);\n },\n onRetry: (retryCount, error, requestConfig) => {\n logger.warn(\n {\n retryCount,\n url: `${requestConfig.baseURL || ''}${requestConfig.url}`,\n error: error.message,\n },\n 'Retrying request'\n );\n },\n };\n axiosRetry(client, retryConfig);\n\n // Circuit breaker\n if (cbOptions.enabled !== false) {\n const breaker = new CircuitBreaker(\n async (config: AxiosRequestConfig) => client.request(config),\n {\n timeout,\n resetTimeout: cbOptions.resetTimeout ?? 30000,\n errorThresholdPercentage: cbOptions.errorThresholdPercentage ?? 50,\n volumeThreshold: 10,\n }\n );\n breaker.on('open', () => logger.warn({ targetService: serviceName }, 'Circuit breaker OPEN'));\n breaker.on('halfOpen', () =>\n logger.info({ targetService: serviceName }, 'Circuit breaker HALF-OPEN')\n );\n breaker.on('close', () =>\n logger.info({ targetService: serviceName }, 'Circuit breaker CLOSED')\n );\n }\n\n return client;\n}\n\nexport type { AxiosInstance, AxiosRequestConfig, AxiosResponse };\n","/**\n * Foundation - Main Entry Point\n */\n\nimport {\n parseConfig,\n type FoundationConfig,\n type FoundationConfigInput,\n} from './config';\nimport {\n createContextManager,\n type ContextManager,\n type RequestContext,\n} from './features/context';\nimport { createLogger, setGlobalLogger, type Logger } from './features/logging';\nimport {\n setupTracing,\n shutdownTracing,\n getTracer,\n getActiveSpan,\n getTraceContext,\n isTracingEnabled,\n SpanStatusCode,\n type Tracer,\n} from './features/tracing';\nimport {\n setupMetrics,\n shutdownMetrics,\n getMeter,\n isMetricsEnabled,\n type Meter,\n} from './features/metrics';\nimport {\n createObservabilityPlugin,\n type FastifyPluginOptions,\n} from './integrations/fastify-plugin';\nimport {\n createHttpClient as createHttpClientInternal,\n type HttpClientOptions,\n type HttpClient,\n} from './integrations/http-client';\n\n// Health status types\nexport interface HealthStatus {\n status: 'healthy' | 'degraded' | 'unhealthy';\n timestamp: string;\n service: string;\n version: string;\n uptime: number;\n components: {\n tracing: ComponentHealth;\n metrics: ComponentHealth;\n logging: ComponentHealth;\n };\n}\n\nexport interface ComponentHealth {\n enabled: boolean;\n status: 'up' | 'down' | 'disabled';\n message?: string;\n}\n\n// Foundation instance interface\nexport interface FoundationInstance {\n readonly config: FoundationConfig;\n readonly logger: Logger;\n readonly context: ContextManager;\n readonly tracer: Tracer | null;\n readonly meter: Meter | null;\n readonly fastifyPlugin: ReturnType<typeof createObservabilityPlugin>;\n readonly features: { tracing: boolean; metrics: boolean; logging: boolean };\n\n getTracer: (name?: string) => Tracer;\n getMeter: (name: string, version?: string) => Meter;\n getTraceContext: () => { traceId?: string; spanId?: string };\n getActiveSpan: () => ReturnType<typeof getActiveSpan>;\n createHttpClient: (options: Omit<HttpClientOptions, 'foundation'>) => HttpClient;\n shutdown: () => Promise<void>;\n isReady: () => boolean;\n\n // Health check\n health: () => HealthStatus;\n\n // Error boundaries\n trace: <T>(name: string, fn: () => T | Promise<T>) => Promise<T>;\n safeRun: <T>(fn: () => T | Promise<T>, fallback?: T) => Promise<T | undefined>;\n}\n\n/** Create a fully configured observability foundation */\nexport function createFoundation(input: FoundationConfigInput): FoundationInstance {\n const startTime = Date.now();\n const config = parseConfig(input);\n const {\n serviceName,\n serviceVersion,\n environment,\n features: featuresConfig,\n otel,\n logging: loggingConfig,\n requestLogging: requestLoggingConfig,\n } = config;\n\n const features = {\n tracing: featuresConfig.tracing ?? true,\n metrics: featuresConfig.metrics ?? true,\n logging: featuresConfig.logging ?? true,\n };\n\n // Context manager\n const contextManager = createContextManager();\n\n // Logger (always created - has fallback)\n let logger: Logger;\n let loggingError: string | undefined;\n try {\n logger = createLogger({\n serviceName,\n serviceVersion,\n environment,\n level: loggingConfig.level ?? 'info',\n prettyPrint: loggingConfig.prettyPrint ?? environment === 'development',\n contextManager,\n });\n setGlobalLogger(logger);\n } catch (err) {\n loggingError = (err as Error).message;\n // Fallback to console\n logger = {\n debug: (obj, msg) => console.debug(JSON.stringify({ ...obj, msg })),\n info: (obj, msg) => console.info(JSON.stringify({ ...obj, msg })),\n warn: (obj, msg) => console.warn(JSON.stringify({ ...obj, msg })),\n error: (obj, msg) => console.error(JSON.stringify({ ...obj, msg })),\n child: () => logger,\n pino: null as never,\n };\n console.error('[neoiq-foundation] Logger setup failed, using console:', loggingError);\n }\n\n // Tracing (with error boundary)\n let tracerInstance: Tracer | null = null;\n let tracingError: string | undefined;\n if (features.tracing) {\n try {\n setupTracing({\n serviceName,\n serviceVersion,\n environment,\n endpoint: otel.endpoint,\n sampleRate: otel.traceSampleRate,\n autoInstrumentation: featuresConfig.autoInstrumentation,\n });\n tracerInstance = getTracer(serviceName);\n logger.info({ feature: 'tracing', endpoint: otel.endpoint }, 'Tracing enabled');\n } catch (err) {\n tracingError = (err as Error).message;\n logger.error({ error: tracingError }, 'Tracing setup failed - continuing without tracing');\n }\n }\n\n // Metrics (with error boundary)\n let meterInstance: Meter | null = null;\n let metricsError: string | undefined;\n if (features.metrics) {\n try {\n setupMetrics({\n serviceName,\n serviceVersion,\n environment,\n endpoint: otel.endpoint,\n intervalMs: otel.metricsIntervalMs,\n });\n meterInstance = getMeter(serviceName);\n logger.info({ feature: 'metrics', interval: `${otel.metricsIntervalMs}ms` }, 'Metrics enabled');\n } catch (err) {\n metricsError = (err as Error).message;\n logger.error({ error: metricsError }, 'Metrics setup failed - continuing without metrics');\n }\n }\n\n logger.info({ serviceName, serviceVersion, environment, features }, 'Foundation initialized');\n\n const foundation: FoundationInstance = {\n config,\n logger,\n context: contextManager,\n tracer: tracerInstance,\n meter: meterInstance,\n features,\n\n getTracer: (name?: string) => getTracer(name || serviceName),\n getMeter: (name: string, version?: string) => getMeter(name, version),\n getTraceContext,\n getActiveSpan,\n\n fastifyPlugin: createObservabilityPlugin({\n serviceName,\n logger,\n contextManager,\n tracingEnabled: features.tracing && !tracingError,\n metricsEnabled: features.metrics && !metricsError,\n requestLogging: requestLoggingConfig,\n }),\n\n createHttpClient: (options) => createHttpClientInternal({ ...options, foundation }),\n\n shutdown: async () => {\n logger.info({}, 'Shutting down foundation...');\n const promises: Promise<void>[] = [];\n if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());\n if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());\n await Promise.all(promises);\n logger.info({}, 'Foundation shutdown complete');\n },\n\n isReady: () => {\n if (features.tracing && !tracingError && !isTracingEnabled()) return false;\n if (features.metrics && !metricsError && !isMetricsEnabled()) return false;\n return true;\n },\n\n // Health check\n health: (): HealthStatus => {\n const tracingUp = !features.tracing || (!tracingError && isTracingEnabled());\n const metricsUp = !features.metrics || (!metricsError && isMetricsEnabled());\n const loggingUp = !loggingError;\n\n const allUp = tracingUp && metricsUp && loggingUp;\n const anyDown =\n (features.tracing && tracingError) ||\n (features.metrics && metricsError) ||\n loggingError;\n\n return {\n status: allUp ? 'healthy' : anyDown ? 'degraded' : 'unhealthy',\n timestamp: new Date().toISOString(),\n service: serviceName,\n version: serviceVersion,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n components: {\n tracing: {\n enabled: features.tracing,\n status: !features.tracing ? 'disabled' : tracingError ? 'down' : 'up',\n message: tracingError,\n },\n metrics: {\n enabled: features.metrics,\n status: !features.metrics ? 'disabled' : metricsError ? 'down' : 'up',\n message: metricsError,\n },\n logging: {\n enabled: features.logging,\n status: loggingError ? 'down' : 'up',\n message: loggingError,\n },\n },\n };\n },\n\n // Trace: Run code within a span (Datadog-style)\n trace: async <T>(name: string, fn: () => T | Promise<T>): Promise<T> => {\n const tracer = tracerInstance || getTracer(serviceName);\n\n return new Promise((resolve, reject) => {\n tracer.startActiveSpan(name, async (span) => {\n try {\n const result = await fn();\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n resolve(result);\n } catch (err) {\n const error = err as Error;\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n span.end();\n logger.error({ span: name, error: error.message }, 'Span failed');\n reject(error);\n }\n });\n });\n },\n\n // Safe run: Execute with try-catch, return fallback on error\n safeRun: async <T>(fn: () => T | Promise<T>, fallback?: T): Promise<T | undefined> => {\n try {\n return await fn();\n } catch (err) {\n const error = err as Error;\n logger.error({ error: error.message, stack: error.stack }, 'safeRun caught error');\n return fallback;\n }\n },\n };\n\n return foundation;\n}\n\n/** Alias for createFoundation */\nexport const setupObservability = createFoundation;\n\nexport { SpanStatusCode };\nexport type { FoundationConfig, FoundationConfigInput };\nexport type { Logger };\nexport type { ContextManager, RequestContext };\nexport type { Tracer, Meter };\nexport type { FastifyPluginOptions };\nexport type { HttpClientOptions, HttpClient };\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciq-dev/neoiq-foundation-node",
|
|
3
|
-
"version": "1.0.0",
|
|
3
|
+
"version": "1.0.1-beta.0",
|
|
4
4
|
"description": "Node.js observability foundation for CommerceIQ services. Integrates with Groundcover via OpenTelemetry.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"files": [
|
|
9
21
|
"dist"
|
|
10
22
|
],
|
|
@@ -24,31 +36,25 @@
|
|
|
24
36
|
"node": ">=18.0.0"
|
|
25
37
|
},
|
|
26
38
|
"peerDependencies": {
|
|
39
|
+
"zod": "^3.22.0",
|
|
27
40
|
"@opentelemetry/api": "^1.7.0",
|
|
28
|
-
"@opentelemetry/auto-instrumentations-node": "
|
|
29
|
-
"@opentelemetry/exporter-metrics-otlp-grpc": "
|
|
30
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "
|
|
31
|
-
"@opentelemetry/resources": "
|
|
32
|
-
"@opentelemetry/sdk-metrics": "
|
|
33
|
-
"@opentelemetry/sdk-node": "
|
|
41
|
+
"@opentelemetry/auto-instrumentations-node": ">=0.41.0",
|
|
42
|
+
"@opentelemetry/exporter-metrics-otlp-grpc": ">=0.48.0",
|
|
43
|
+
"@opentelemetry/exporter-trace-otlp-grpc": ">=0.48.0",
|
|
44
|
+
"@opentelemetry/resources": ">=1.21.0",
|
|
45
|
+
"@opentelemetry/sdk-metrics": ">=1.21.0",
|
|
46
|
+
"@opentelemetry/sdk-node": ">=0.48.0",
|
|
34
47
|
"@opentelemetry/semantic-conventions": "^1.21.0",
|
|
35
48
|
"axios": "^1.6.0",
|
|
36
49
|
"axios-retry": "^4.0.0",
|
|
37
|
-
"fastify": "
|
|
38
|
-
"fastify-plugin": "
|
|
39
|
-
"opossum": "
|
|
40
|
-
"pino": "
|
|
41
|
-
"pino-pretty": "
|
|
42
|
-
},
|
|
43
|
-
"np": {
|
|
44
|
-
"branch": "main",
|
|
45
|
-
"anyBranch": true,
|
|
46
|
-
"noYarn": true,
|
|
47
|
-
"yolo": true,
|
|
48
|
-
"tag": "latest"
|
|
50
|
+
"fastify": ">=4.25.0",
|
|
51
|
+
"fastify-plugin": ">=4.5.0",
|
|
52
|
+
"opossum": ">=8.1.0",
|
|
53
|
+
"pino": ">=8.17.0",
|
|
54
|
+
"pino-pretty": ">=10.3.0"
|
|
49
55
|
},
|
|
50
56
|
"repository": {
|
|
51
57
|
"type": "git",
|
|
52
|
-
"url": "git+https://
|
|
58
|
+
"url": "git+https://github.com/commerceiq/neoiq-foundation-node.git"
|
|
53
59
|
}
|
|
54
60
|
}
|
package/dist/http-client.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Client with observability, retry, and circuit breaker.
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - OpenTelemetry trace context propagation (traceparent header)
|
|
6
|
-
* - Correlation ID propagation (x-request-id header)
|
|
7
|
-
* - HTTP client metrics (request count, duration, errors)
|
|
8
|
-
* - Automatic retries with exponential backoff
|
|
9
|
-
* - Circuit breaker pattern
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* import { createHttpClient } from '@commerceiq/neoiq-foundation-node';
|
|
13
|
-
*
|
|
14
|
-
* const client = createHttpClient({
|
|
15
|
-
* baseURL: 'https://api.example.com',
|
|
16
|
-
* serviceName: 'example-api',
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* const response = await client.get('/users');
|
|
20
|
-
*/
|
|
21
|
-
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
22
|
-
export interface HttpClientOptions {
|
|
23
|
-
/**
|
|
24
|
-
* Base URL for all requests.
|
|
25
|
-
*/
|
|
26
|
-
baseURL: string;
|
|
27
|
-
/**
|
|
28
|
-
* Name of the target service (for metrics and logging).
|
|
29
|
-
*/
|
|
30
|
-
serviceName: string;
|
|
31
|
-
/**
|
|
32
|
-
* Request timeout in milliseconds (default: 30000).
|
|
33
|
-
*/
|
|
34
|
-
timeout?: number;
|
|
35
|
-
/**
|
|
36
|
-
* Retry configuration.
|
|
37
|
-
*/
|
|
38
|
-
retry?: {
|
|
39
|
-
/**
|
|
40
|
-
* Maximum number of retries (default: 3).
|
|
41
|
-
*/
|
|
42
|
-
retries?: number;
|
|
43
|
-
/**
|
|
44
|
-
* Base delay in milliseconds for exponential backoff (default: 1000).
|
|
45
|
-
*/
|
|
46
|
-
retryDelay?: number;
|
|
47
|
-
/**
|
|
48
|
-
* HTTP status codes to retry on (default: [408, 429, 500, 502, 503, 504]).
|
|
49
|
-
*/
|
|
50
|
-
retryStatusCodes?: number[];
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Circuit breaker configuration.
|
|
54
|
-
*/
|
|
55
|
-
circuitBreaker?: {
|
|
56
|
-
/**
|
|
57
|
-
* Whether to enable circuit breaker (default: true).
|
|
58
|
-
*/
|
|
59
|
-
enabled?: boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Time in ms before attempting to close the circuit (default: 30000).
|
|
62
|
-
*/
|
|
63
|
-
resetTimeout?: number;
|
|
64
|
-
/**
|
|
65
|
-
* Error percentage threshold to open the circuit (default: 50).
|
|
66
|
-
*/
|
|
67
|
-
errorThresholdPercentage?: number;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* Additional default headers.
|
|
71
|
-
*/
|
|
72
|
-
headers?: Record<string, string>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Create a configured HTTP client with full observability.
|
|
76
|
-
* Metrics are exported to OTEL Collector → Groundcover.
|
|
77
|
-
*/
|
|
78
|
-
export declare function createHttpClient(options: HttpClientOptions): AxiosInstance;
|
|
79
|
-
export { AxiosInstance, AxiosRequestConfig, AxiosResponse };
|
|
80
|
-
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAc,EAAE,aAAa,EAAE,kBAAkB,EAA8B,aAAa,EAAE,MAAM,OAAO,CAAC;AAU5G,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,KAAK,CAAC,EAAE;QACN;;WAEG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB;;WAEG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QAEpB;;WAEG;QACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC7B,CAAC;IAEF;;OAEG;IACH,cAAc,CAAC,EAAE;QACf;;WAEG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;QAElB;;WAEG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB;;WAEG;QACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;KACnC,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,aAAa,CAqM1E;AAGD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/http-client.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* HTTP Client with observability, retry, and circuit breaker.
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - OpenTelemetry trace context propagation (traceparent header)
|
|
7
|
-
* - Correlation ID propagation (x-request-id header)
|
|
8
|
-
* - HTTP client metrics (request count, duration, errors)
|
|
9
|
-
* - Automatic retries with exponential backoff
|
|
10
|
-
* - Circuit breaker pattern
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* import { createHttpClient } from '@commerceiq/neoiq-foundation-node';
|
|
14
|
-
*
|
|
15
|
-
* const client = createHttpClient({
|
|
16
|
-
* baseURL: 'https://api.example.com',
|
|
17
|
-
* serviceName: 'example-api',
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* const response = await client.get('/users');
|
|
21
|
-
*/
|
|
22
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.createHttpClient = createHttpClient;
|
|
27
|
-
const axios_1 = __importDefault(require("axios"));
|
|
28
|
-
const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
29
|
-
const opossum_1 = __importDefault(require("opossum"));
|
|
30
|
-
const api_1 = require("@opentelemetry/api");
|
|
31
|
-
const observability_1 = require("./observability");
|
|
32
|
-
// -----------------------------------------------------------------------------
|
|
33
|
-
// HTTP Client Factory
|
|
34
|
-
// -----------------------------------------------------------------------------
|
|
35
|
-
/**
|
|
36
|
-
* Create a configured HTTP client with full observability.
|
|
37
|
-
* Metrics are exported to OTEL Collector → Groundcover.
|
|
38
|
-
*/
|
|
39
|
-
function createHttpClient(options) {
|
|
40
|
-
const { baseURL, serviceName, timeout = 30000, retry = {}, circuitBreaker: cbOptions = {}, headers = {}, } = options;
|
|
41
|
-
// Create Axios instance
|
|
42
|
-
const client = axios_1.default.create({
|
|
43
|
-
baseURL,
|
|
44
|
-
timeout,
|
|
45
|
-
headers: {
|
|
46
|
-
'Content-Type': 'application/json',
|
|
47
|
-
...headers,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
// Setup metrics (per Groundcover guide)
|
|
51
|
-
const meter = (0, observability_1.getMeter)(`http-client-${serviceName}`);
|
|
52
|
-
const requestCounter = meter.createCounter('http.client.requests.total', {
|
|
53
|
-
description: 'Total number of outbound HTTP requests',
|
|
54
|
-
});
|
|
55
|
-
const requestDuration = meter.createHistogram('http.client.request.duration', {
|
|
56
|
-
description: 'Outbound HTTP request duration in milliseconds',
|
|
57
|
-
unit: 'ms',
|
|
58
|
-
});
|
|
59
|
-
const requestErrors = meter.createCounter('http.client.requests.errors', {
|
|
60
|
-
description: 'Total number of outbound HTTP request errors',
|
|
61
|
-
});
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Request Interceptor - Add trace context and correlation ID
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
client.interceptors.request.use((config) => {
|
|
66
|
-
// 1. Propagate OpenTelemetry trace context (W3C traceparent)
|
|
67
|
-
const carrier = {};
|
|
68
|
-
api_1.propagation.inject(api_1.context.active(), carrier);
|
|
69
|
-
if (carrier.traceparent) {
|
|
70
|
-
config.headers.set('traceparent', carrier.traceparent);
|
|
71
|
-
}
|
|
72
|
-
if (carrier.tracestate) {
|
|
73
|
-
config.headers.set('tracestate', carrier.tracestate);
|
|
74
|
-
}
|
|
75
|
-
// 2. Propagate correlation ID
|
|
76
|
-
const reqCtx = (0, observability_1.getRequestContext)();
|
|
77
|
-
if (reqCtx?.correlationId) {
|
|
78
|
-
config.headers.set('x-request-id', reqCtx.correlationId);
|
|
79
|
-
}
|
|
80
|
-
// 3. Add timing metadata
|
|
81
|
-
config.__startTime = Date.now();
|
|
82
|
-
// 4. Log outbound request
|
|
83
|
-
observability_1.logger.debug({
|
|
84
|
-
method: config.method?.toUpperCase(),
|
|
85
|
-
url: `${config.baseURL || ''}${config.url}`,
|
|
86
|
-
targetService: serviceName,
|
|
87
|
-
correlationId: reqCtx?.correlationId,
|
|
88
|
-
}, 'Outbound HTTP request');
|
|
89
|
-
return config;
|
|
90
|
-
});
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Response Interceptor - Log and record metrics
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
client.interceptors.response.use((response) => {
|
|
95
|
-
const config = response.config;
|
|
96
|
-
const durationMs = Date.now() - (config.__startTime || Date.now());
|
|
97
|
-
const reqCtx = (0, observability_1.getRequestContext)();
|
|
98
|
-
const labels = {
|
|
99
|
-
target_service: serviceName,
|
|
100
|
-
method: config.method?.toUpperCase() || 'GET',
|
|
101
|
-
status_code: String(response.status),
|
|
102
|
-
};
|
|
103
|
-
// Log success
|
|
104
|
-
observability_1.logger.debug({
|
|
105
|
-
method: config.method?.toUpperCase(),
|
|
106
|
-
url: `${config.baseURL || ''}${config.url}`,
|
|
107
|
-
targetService: serviceName,
|
|
108
|
-
statusCode: response.status,
|
|
109
|
-
durationMs,
|
|
110
|
-
correlationId: reqCtx?.correlationId,
|
|
111
|
-
}, 'Outbound HTTP response');
|
|
112
|
-
// Record metrics
|
|
113
|
-
requestCounter.add(1, labels);
|
|
114
|
-
requestDuration.record(durationMs, labels);
|
|
115
|
-
return response;
|
|
116
|
-
}, (error) => {
|
|
117
|
-
const config = error.config;
|
|
118
|
-
const durationMs = config ? Date.now() - (config.__startTime || Date.now()) : 0;
|
|
119
|
-
const statusCode = error.response?.status || 0;
|
|
120
|
-
const reqCtx = (0, observability_1.getRequestContext)();
|
|
121
|
-
const labels = {
|
|
122
|
-
target_service: serviceName,
|
|
123
|
-
method: config?.method?.toUpperCase() || 'GET',
|
|
124
|
-
status_code: String(statusCode),
|
|
125
|
-
};
|
|
126
|
-
// Log error
|
|
127
|
-
observability_1.logger.error({
|
|
128
|
-
method: config?.method?.toUpperCase(),
|
|
129
|
-
url: config ? `${config.baseURL || ''}${config.url}` : 'unknown',
|
|
130
|
-
targetService: serviceName,
|
|
131
|
-
statusCode,
|
|
132
|
-
durationMs,
|
|
133
|
-
error: error.message,
|
|
134
|
-
correlationId: reqCtx?.correlationId,
|
|
135
|
-
}, 'Outbound HTTP error');
|
|
136
|
-
// Record metrics
|
|
137
|
-
requestCounter.add(1, labels);
|
|
138
|
-
requestDuration.record(durationMs, labels);
|
|
139
|
-
requestErrors.add(1, labels);
|
|
140
|
-
return Promise.reject(error);
|
|
141
|
-
});
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// Configure Retry
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
const retryConfig = {
|
|
146
|
-
retries: retry.retries ?? 3,
|
|
147
|
-
retryDelay: (retryCount) => {
|
|
148
|
-
const baseDelay = retry.retryDelay ?? 1000;
|
|
149
|
-
return baseDelay * Math.pow(2, retryCount - 1); // Exponential backoff
|
|
150
|
-
},
|
|
151
|
-
retryCondition: (error) => {
|
|
152
|
-
const retryStatusCodes = retry.retryStatusCodes ?? [408, 429, 500, 502, 503, 504];
|
|
153
|
-
const status = error.response?.status;
|
|
154
|
-
return !error.response || retryStatusCodes.includes(status || 0);
|
|
155
|
-
},
|
|
156
|
-
onRetry: (retryCount, error, requestConfig) => {
|
|
157
|
-
observability_1.logger.warn({
|
|
158
|
-
retryCount,
|
|
159
|
-
url: `${requestConfig.baseURL || ''}${requestConfig.url}`,
|
|
160
|
-
error: error.message,
|
|
161
|
-
targetService: serviceName,
|
|
162
|
-
}, 'Retrying HTTP request');
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
(0, axios_retry_1.default)(client, retryConfig);
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
// Configure Circuit Breaker (optional wrapper)
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
if (cbOptions.enabled !== false) {
|
|
170
|
-
const breaker = new opossum_1.default(async (config) => client.request(config), {
|
|
171
|
-
timeout,
|
|
172
|
-
resetTimeout: cbOptions.resetTimeout ?? 30000,
|
|
173
|
-
errorThresholdPercentage: cbOptions.errorThresholdPercentage ?? 50,
|
|
174
|
-
volumeThreshold: 10,
|
|
175
|
-
});
|
|
176
|
-
breaker.on('open', () => {
|
|
177
|
-
observability_1.logger.warn({ targetService: serviceName, baseURL }, 'Circuit breaker OPEN');
|
|
178
|
-
});
|
|
179
|
-
breaker.on('halfOpen', () => {
|
|
180
|
-
observability_1.logger.info({ targetService: serviceName, baseURL }, 'Circuit breaker HALF-OPEN');
|
|
181
|
-
});
|
|
182
|
-
breaker.on('close', () => {
|
|
183
|
-
observability_1.logger.info({ targetService: serviceName, baseURL }, 'Circuit breaker CLOSED');
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
return client;
|
|
187
|
-
}
|
|
188
|
-
//# sourceMappingURL=http-client.js.map
|
package/dist/http-client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
|
package/dist/observability.d.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenTelemetry initialization for Node.js services.
|
|
3
|
-
*
|
|
4
|
-
* Follows Groundcover Integration Guide - Option 2 (Via OTEL Collector)
|
|
5
|
-
*
|
|
6
|
-
* Flow: App → OpenTelemetry SDK → OTEL Collector → Groundcover
|
|
7
|
-
*
|
|
8
|
-
* Environment Variables:
|
|
9
|
-
* OTEL_EXPORTER_OTLP_ENDPOINT: Collector URL (default: cluster OTEL Collector)
|
|
10
|
-
* OTEL_SERVICE_NAME: Service name
|
|
11
|
-
* OTEL_SERVICE_VERSION: Service version (default: 1.0.0)
|
|
12
|
-
* OTEL_ENVIRONMENT: Deployment environment (default: development)
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* import { init, logger, getMeter } from '@commerceiq/neoiq-foundation-node';
|
|
16
|
-
*
|
|
17
|
-
* init({ serviceName: 'my-service' });
|
|
18
|
-
*
|
|
19
|
-
* logger.info({ action: 'startup' }, 'Service started');
|
|
20
|
-
*
|
|
21
|
-
* const meter = getMeter('my-service');
|
|
22
|
-
* const counter = meter.createCounter('requests_total');
|
|
23
|
-
*/
|
|
24
|
-
import { SpanStatusCode } from '@opentelemetry/api';
|
|
25
|
-
import pino from 'pino';
|
|
26
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
27
|
-
export interface InitOptions {
|
|
28
|
-
/**
|
|
29
|
-
* Service name (required). Shows up in Groundcover.
|
|
30
|
-
*/
|
|
31
|
-
serviceName: string;
|
|
32
|
-
/**
|
|
33
|
-
* Service version (default: 1.0.0).
|
|
34
|
-
*/
|
|
35
|
-
serviceVersion?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Deployment environment (default: development).
|
|
38
|
-
*/
|
|
39
|
-
environment?: string;
|
|
40
|
-
/**
|
|
41
|
-
* OTEL Collector endpoint.
|
|
42
|
-
* Default: http://otel-stack-deployment-collector.observability.svc.cluster.local:4317
|
|
43
|
-
*/
|
|
44
|
-
otlpEndpoint?: string;
|
|
45
|
-
/**
|
|
46
|
-
* Log level (default: info).
|
|
47
|
-
*/
|
|
48
|
-
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
49
|
-
/**
|
|
50
|
-
* Metrics export interval in ms (default: 5000 per Groundcover guide).
|
|
51
|
-
*/
|
|
52
|
-
metricsIntervalMs?: number;
|
|
53
|
-
}
|
|
54
|
-
export interface RequestContext {
|
|
55
|
-
correlationId?: string;
|
|
56
|
-
traceId?: string;
|
|
57
|
-
spanId?: string;
|
|
58
|
-
}
|
|
59
|
-
export declare const als: AsyncLocalStorage<RequestContext>;
|
|
60
|
-
/**
|
|
61
|
-
* Initialize OpenTelemetry SDK with traces and metrics.
|
|
62
|
-
*
|
|
63
|
-
* Sends telemetry to OTEL Collector which forwards to Groundcover.
|
|
64
|
-
* Call this at the very start of your application, before other imports.
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* init({ serviceName: 'canvas-weaver' });
|
|
68
|
-
*/
|
|
69
|
-
export declare function init(options: InitOptions): void;
|
|
70
|
-
/**
|
|
71
|
-
* Structured logger with automatic trace context injection.
|
|
72
|
-
* All logs include traceId, spanId, and correlationId when available.
|
|
73
|
-
*/
|
|
74
|
-
export declare const logger: {
|
|
75
|
-
info: (obj: object, msg?: string) => void;
|
|
76
|
-
error: (obj: object, msg?: string) => void;
|
|
77
|
-
warn: (obj: object, msg?: string) => void;
|
|
78
|
-
debug: (obj: object, msg?: string) => void;
|
|
79
|
-
child: (bindings: object) => pino.Logger<never, boolean>;
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Get a tracer instance for creating spans.
|
|
83
|
-
*
|
|
84
|
-
* @param name - Tracer name (defaults to service name)
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* const tracer = getTracer();
|
|
88
|
-
* tracer.startActiveSpan('db.query', (span) => {
|
|
89
|
-
* // ... do work
|
|
90
|
-
* span.end();
|
|
91
|
-
* });
|
|
92
|
-
*/
|
|
93
|
-
export declare function getTracer(name?: string): import("@opentelemetry/api").Tracer;
|
|
94
|
-
/**
|
|
95
|
-
* Get a meter instance for creating metrics.
|
|
96
|
-
* Metrics are exported to OTEL Collector → Groundcover.
|
|
97
|
-
*
|
|
98
|
-
* @param name - Meter name (typically service name)
|
|
99
|
-
* @param version - Meter version (default: 1.0.0)
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* const meter = getMeter('canvas-weaver');
|
|
103
|
-
* const counter = meter.createCounter('http.requests.total');
|
|
104
|
-
* counter.add(1, { method: 'GET', route: '/api/reports' });
|
|
105
|
-
*
|
|
106
|
-
* const histogram = meter.createHistogram('http.request.duration');
|
|
107
|
-
* histogram.record(150, { method: 'GET', route: '/api/reports' });
|
|
108
|
-
*/
|
|
109
|
-
export declare function getMeter(name: string, version?: string): import("@opentelemetry/api").Meter;
|
|
110
|
-
/**
|
|
111
|
-
* Get current request context from AsyncLocalStorage.
|
|
112
|
-
*/
|
|
113
|
-
export declare function getRequestContext(): RequestContext | undefined;
|
|
114
|
-
/**
|
|
115
|
-
* Run a function with a specific context (for manual context propagation).
|
|
116
|
-
*/
|
|
117
|
-
export declare function runWithContext<T>(ctx: RequestContext, fn: () => T): T;
|
|
118
|
-
/**
|
|
119
|
-
* Get current trace context as a dictionary.
|
|
120
|
-
* Useful for logging or passing to external systems.
|
|
121
|
-
*/
|
|
122
|
-
export declare function getTraceContext(): {
|
|
123
|
-
traceId?: string;
|
|
124
|
-
spanId?: string;
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Gracefully shutdown OTEL providers.
|
|
128
|
-
* Call this on application shutdown to flush pending telemetry.
|
|
129
|
-
*/
|
|
130
|
-
export declare function shutdown(): Promise<void>;
|
|
131
|
-
export { SpanStatusCode };
|
|
132
|
-
//# sourceMappingURL=observability.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAYH,OAAO,EAAkB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAUhD,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAE/C;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,GAAG,mCAA0C,CAAC;AAgB3D;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CA2F/C;AAMD;;;GAGG;AACH,eAAO,MAAM,MAAM;gBACL,MAAM,QAAQ,MAAM;iBACnB,MAAM,QAAQ,MAAM;gBACrB,MAAM,QAAQ,MAAM;iBACnB,MAAM,QAAQ,MAAM;sBACf,MAAM;CACzB,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,uCAEtC;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAgB,sCAE/D;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErE;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CASvE;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAU9C;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|