UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

304 lines 9.45 kB
/** * Span Processor * Handles span processing before export - enrichment, filtering, and transformation * Fills the 9% gap in pattern compliance */ /** * No-op processor that passes spans through unchanged */ export class PassThroughProcessor { name = "pass-through"; process(span) { return span; } } /** * Attribute enrichment processor * Adds additional attributes to spans based on configuration */ export class AttributeEnrichmentProcessor { name = "attribute-enrichment"; staticAttributes; dynamicAttributes; constructor(config) { this.staticAttributes = config.staticAttributes ?? {}; this.dynamicAttributes = config.dynamicAttributes ?? (() => ({})); } process(span) { return { ...span, attributes: { ...this.staticAttributes, ...span.attributes, ...this.dynamicAttributes(span), }, }; } } /** * Filter processor - drops spans based on conditions */ export class FilterProcessor { name = "filter"; predicate; constructor(predicate) { this.predicate = predicate; } process(span) { return this.predicate(span) ? span : null; } } /** * Redaction processor - removes sensitive data from spans */ export class RedactionProcessor { name = "redaction"; sensitiveKeys; redactedValue; constructor(config) { this.sensitiveKeys = new Set(config?.sensitiveKeys ?? [ "api_key", "apiKey", "secret", "password", "token", "authorization", "credentials", "private_key", "privateKey", "stack", "error.stack", ]); this.redactedValue = config?.redactedValue ?? "[REDACTED]"; } process(span) { const redactedAttributes = {}; for (const [key, value] of Object.entries(span.attributes)) { const lowerKey = key.toLowerCase(); const isSensitive = Array.from(this.sensitiveKeys).some((sensitiveKey) => lowerKey.includes(sensitiveKey.toLowerCase())); if (isSensitive && typeof value === "string") { redactedAttributes[key] = this.redactedValue; } else if (typeof value === "object" && value !== null) { redactedAttributes[key] = this.redactObject(value); } else { redactedAttributes[key] = value; } } return { ...span, attributes: redactedAttributes, }; } redactObject(obj) { if (typeof obj !== "object" || obj === null) { return obj; } if (Array.isArray(obj)) { return obj.map((item) => this.redactObject(item)); } const result = {}; for (const [key, value] of Object.entries(obj)) { const lowerKey = key.toLowerCase(); const isSensitive = Array.from(this.sensitiveKeys).some((sensitiveKey) => lowerKey.includes(sensitiveKey.toLowerCase())); if (isSensitive && typeof value === "string") { result[key] = this.redactedValue; } else if (typeof value === "object" && value !== null) { result[key] = this.redactObject(value); } else { result[key] = value; } } return result; } } /** * Truncation processor - truncates large attribute values */ export class TruncationProcessor { name = "truncation"; maxStringLength; maxArrayLength; constructor(config) { this.maxStringLength = config?.maxStringLength ?? 10000; this.maxArrayLength = config?.maxArrayLength ?? 100; } process(span) { return { ...span, attributes: this.truncateAttributes(span.attributes), }; } truncateAttributes(attrs) { const result = {}; for (const [key, value] of Object.entries(attrs)) { result[key] = this.truncateValue(value); } return result; } truncateValue(value) { if (typeof value === "string" && value.length > this.maxStringLength) { return value.substring(0, this.maxStringLength) + "...[truncated]"; } if (Array.isArray(value) && value.length > this.maxArrayLength) { return [ ...value.slice(0, this.maxArrayLength), `...[${value.length - this.maxArrayLength} more items]`, ]; } if (typeof value === "object" && value !== null) { const result = {}; for (const [k, v] of Object.entries(value)) { result[k] = this.truncateValue(v); } return result; } return value; } } /** * Composite processor - chains multiple processors together */ export class CompositeProcessor { name = "composite"; processors; constructor(processors) { this.processors = processors; } process(span) { let result = span; for (const processor of this.processors) { if (result === null) { return null; } result = processor.process(result); } return result; } async processAsync(span) { let result = span; for (const processor of this.processors) { if (result === null) { return null; } if (processor.processAsync) { result = await processor.processAsync(result); } else { result = processor.process(result); } } return result; } async shutdown() { await Promise.all(this.processors.map((p) => p.shutdown ? p.shutdown() : Promise.resolve())); } } /** * Batch processor - collects spans and processes them in batches */ export class BatchProcessor { name = "batch"; innerProcessor; batchSize; flushIntervalMs; batch = []; flushTimer = null; onBatchReady; constructor(config) { this.innerProcessor = config.processor ?? new PassThroughProcessor(); this.batchSize = config.batchSize ?? 100; this.flushIntervalMs = config.flushIntervalMs ?? 5000; this.onBatchReady = config.onBatchReady; this.startFlushTimer(); } process(span) { const processed = this.innerProcessor.process(span); if (processed) { this.batch.push(processed); if (this.batch.length >= this.batchSize) { this.flush(); } } return processed; } startFlushTimer() { this.flushTimer = setInterval(() => { this.flush(); }, this.flushIntervalMs); } // Note: flush() is intentionally synchronous. The onBatchReady callback is // typed as `(spans: SpanData[]) => void` — callers must not pass async // exporters. If async export is needed, the callback should handle its own // error reporting (e.g. fire-and-forget with promise error handlers). flush() { if (this.batch.length > 0 && this.onBatchReady) { const spans = [...this.batch]; try { this.onBatchReady(spans); this.batch = []; } catch (flushError) { // Keep spans for next flush attempt, but cap backlog growth void flushError; // acknowledged — error is expected during exporter outages const maxBacklog = this.batchSize * 20; if (this.batch.length > maxBacklog) { this.batch = this.batch.slice(this.batch.length - maxBacklog); } } } } async shutdown() { if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } this.flush(); if (this.innerProcessor.shutdown) { await this.innerProcessor.shutdown(); } } } /** * Factory for creating span processors */ export class SpanProcessorFactory { /** * Create a standard processor pipeline for production use */ static createProductionPipeline(config) { const processors = [ // Add service context new AttributeEnrichmentProcessor({ staticAttributes: { "service.name": config?.serviceName ?? "neurolink", "deployment.environment": config?.environment ?? "production", }, }), // Redact sensitive data new RedactionProcessor(), // Truncate large values new TruncationProcessor(), ]; if (config?.additionalProcessors) { processors.push(...config.additionalProcessors); } return new CompositeProcessor(processors); } /** * Create a minimal processor pipeline for development */ static createDevelopmentPipeline() { return new CompositeProcessor([ new AttributeEnrichmentProcessor({ staticAttributes: { "deployment.environment": "development", }, }), ]); } } //# sourceMappingURL=spanProcessor.js.map