@vtvlive/interactive-apm 0.0.2
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/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/factories/tracing-provider.factory.d.ts +81 -0
- package/dist/factories/tracing-provider.factory.d.ts.map +1 -0
- package/dist/factories/tracing-provider.factory.js +93 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/init/elastic-apm-init.d.ts +51 -0
- package/dist/init/elastic-apm-init.d.ts.map +1 -0
- package/dist/init/elastic-apm-init.js +86 -0
- package/dist/init/opentelemetry-init.d.ts +51 -0
- package/dist/init/opentelemetry-init.d.ts.map +1 -0
- package/dist/init/opentelemetry-init.js +156 -0
- package/dist/interfaces/tracing-provider.interface.d.ts +43 -0
- package/dist/interfaces/tracing-provider.interface.d.ts.map +1 -0
- package/dist/interfaces/tracing-provider.interface.js +2 -0
- package/dist/modules/tracing.module.d.ts +96 -0
- package/dist/modules/tracing.module.d.ts.map +1 -0
- package/dist/modules/tracing.module.js +162 -0
- package/dist/providers/elastic-apm.tracing-provider.d.ts +57 -0
- package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -0
- package/dist/providers/elastic-apm.tracing-provider.js +163 -0
- package/dist/providers/opentelemetry.tracing-provider.d.ts +68 -0
- package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -0
- package/dist/providers/opentelemetry.tracing-provider.js +288 -0
- package/dist/services/tracing.service.d.ts +88 -0
- package/dist/services/tracing.service.d.ts.map +1 -0
- package/dist/services/tracing.service.js +103 -0
- package/dist/types/apm-provider.type.d.ts +47 -0
- package/dist/types/apm-provider.type.d.ts.map +1 -0
- package/dist/types/apm-provider.type.js +32 -0
- package/dist/utils/tracing.helper.d.ts +68 -0
- package/dist/utils/tracing.helper.d.ts.map +1 -0
- package/dist/utils/tracing.helper.js +115 -0
- package/package.json +105 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Elastic APM Initialization (Standalone)
|
|
4
|
+
*
|
|
5
|
+
* Use this to initialize Elastic APM agent before your application starts.
|
|
6
|
+
* This is useful for non-NestJS applications or when you need manual control.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { initElasticApm } from '@vtvlive/interactive-apm';
|
|
10
|
+
*
|
|
11
|
+
* // Initialize at the top of your application
|
|
12
|
+
* initElasticApm({
|
|
13
|
+
* serviceName: 'my-service',
|
|
14
|
+
* serverUrl: 'http://localhost:8200',
|
|
15
|
+
* environment: 'production',
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.shouldUseElasticApm = shouldUseElasticApm;
|
|
20
|
+
exports.initElasticApm = initElasticApm;
|
|
21
|
+
exports.isElasticApmStarted = isElasticApmStarted;
|
|
22
|
+
exports.getElasticApmAgent = getElasticApmAgent;
|
|
23
|
+
const apm_provider_type_1 = require("../types/apm-provider.type");
|
|
24
|
+
/**
|
|
25
|
+
* Check if APM should use Elastic APM provider based on configuration
|
|
26
|
+
* @returns true if Elastic APM should be used
|
|
27
|
+
*/
|
|
28
|
+
function shouldUseElasticApm() {
|
|
29
|
+
const provider = process.env.APM_PROVIDER || apm_provider_type_1.ApmProvider.OPENTELEMETRY;
|
|
30
|
+
return provider === apm_provider_type_1.ApmProvider.ELASTIC_APM || provider === 'elastic-apm';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize Elastic APM agent for standalone usage
|
|
34
|
+
*
|
|
35
|
+
* @param options Configuration options
|
|
36
|
+
* @returns The APM agent instance
|
|
37
|
+
*/
|
|
38
|
+
function initElasticApm(options = {}) {
|
|
39
|
+
// @ts-ignore - Optional peer dependency
|
|
40
|
+
const apm = require('elastic-apm-node');
|
|
41
|
+
// If already started, return the instance
|
|
42
|
+
if (apm.isStarted()) {
|
|
43
|
+
return apm;
|
|
44
|
+
}
|
|
45
|
+
// Start the agent
|
|
46
|
+
const agent = apm.start({
|
|
47
|
+
serviceName: options.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend',
|
|
48
|
+
serverUrl: options.serverUrl || process.env.ELASTIC_APM_SERVER_URL || 'http://localhost:8200',
|
|
49
|
+
secretToken: options.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN,
|
|
50
|
+
serviceVersion: options.serviceVersion || process.env.npm_package_version || '1.0.0',
|
|
51
|
+
environment: options.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development',
|
|
52
|
+
logLevel: options.logLevel || 'error',
|
|
53
|
+
});
|
|
54
|
+
console.log(`[ElasticAPM] Service: ${agent.getServiceName()}, Environment: ${agent.conf.environment}`);
|
|
55
|
+
console.log('[ElasticAPM] Initialized');
|
|
56
|
+
return agent;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if Elastic APM agent is started
|
|
60
|
+
*/
|
|
61
|
+
function isElasticApmStarted() {
|
|
62
|
+
try {
|
|
63
|
+
// @ts-ignore - Optional peer dependency
|
|
64
|
+
const apm = require('elastic-apm-node');
|
|
65
|
+
return apm.isStarted();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the Elastic APM agent instance
|
|
73
|
+
*/
|
|
74
|
+
function getElasticApmAgent() {
|
|
75
|
+
try {
|
|
76
|
+
// @ts-ignore - Optional peer dependency
|
|
77
|
+
const apm = require('elastic-apm-node');
|
|
78
|
+
if (apm.isStarted()) {
|
|
79
|
+
return apm;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Initialization (Standalone)
|
|
3
|
+
*
|
|
4
|
+
* Use this to initialize OpenTelemetry SDK for standalone usage.
|
|
5
|
+
* This is useful for non-NestJS applications or when you need manual control.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { initOpenTelemetry } from '@vtvlive/interactive-apm';
|
|
9
|
+
*
|
|
10
|
+
* // Initialize at the top of your application
|
|
11
|
+
* const sdk = initOpenTelemetry({
|
|
12
|
+
* serviceName: 'my-service',
|
|
13
|
+
* otlpEndpoint: 'http://localhost:8200/v1/traces',
|
|
14
|
+
* environment: 'production',
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
export interface OpenTelemetryInitOptions {
|
|
18
|
+
/** Service name for APM */
|
|
19
|
+
serviceName?: string;
|
|
20
|
+
/** OTLP endpoint URL */
|
|
21
|
+
otlpEndpoint?: string;
|
|
22
|
+
/** Secret token for authentication */
|
|
23
|
+
secretToken?: string;
|
|
24
|
+
/** OTLP auth token (takes precedence over secretToken for OTLP) */
|
|
25
|
+
otlpAuthToken?: string;
|
|
26
|
+
/** OTLP headers as a record (key-value pairs). If provided, merges with Authorization header when token is set. */
|
|
27
|
+
otlpHeaders?: Record<string, string>;
|
|
28
|
+
/** Service version */
|
|
29
|
+
serviceVersion?: string;
|
|
30
|
+
/** Deployment environment */
|
|
31
|
+
environment?: string;
|
|
32
|
+
/** Enable console exporter for debugging */
|
|
33
|
+
enableConsoleExporter?: boolean;
|
|
34
|
+
/** Enable HTTP instrumentation */
|
|
35
|
+
enableHttpInstrumentation?: boolean;
|
|
36
|
+
/** Enable Express instrumentation */
|
|
37
|
+
enableExpressInstrumentation?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if APM should use OpenTelemetry provider based on configuration
|
|
41
|
+
* @returns true if OpenTelemetry should be used
|
|
42
|
+
*/
|
|
43
|
+
export declare function shouldUseOpenTelemetry(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Initialize OpenTelemetry SDK for standalone usage
|
|
46
|
+
*
|
|
47
|
+
* @param options Configuration options
|
|
48
|
+
* @returns The NodeSDK instance
|
|
49
|
+
*/
|
|
50
|
+
export declare function initOpenTelemetry(options?: OpenTelemetryInitOptions): Promise<any>;
|
|
51
|
+
//# sourceMappingURL=opentelemetry-init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opentelemetry-init.d.ts","sourceRoot":"","sources":["../../src/init/opentelemetry-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,wBAAwB;IACvC,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,sBAAsB;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kCAAkC;IAClC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,qCAAqC;IACrC,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAGhD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,GAAG,CAAC,CAyG5F"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry Initialization (Standalone)
|
|
4
|
+
*
|
|
5
|
+
* Use this to initialize OpenTelemetry SDK for standalone usage.
|
|
6
|
+
* This is useful for non-NestJS applications or when you need manual control.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { initOpenTelemetry } from '@vtvlive/interactive-apm';
|
|
10
|
+
*
|
|
11
|
+
* // Initialize at the top of your application
|
|
12
|
+
* const sdk = initOpenTelemetry({
|
|
13
|
+
* serviceName: 'my-service',
|
|
14
|
+
* otlpEndpoint: 'http://localhost:8200/v1/traces',
|
|
15
|
+
* environment: 'production',
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.shouldUseOpenTelemetry = shouldUseOpenTelemetry;
|
|
53
|
+
exports.initOpenTelemetry = initOpenTelemetry;
|
|
54
|
+
const apm_provider_type_1 = require("../types/apm-provider.type");
|
|
55
|
+
/**
|
|
56
|
+
* Check if APM should use OpenTelemetry provider based on configuration
|
|
57
|
+
* @returns true if OpenTelemetry should be used
|
|
58
|
+
*/
|
|
59
|
+
function shouldUseOpenTelemetry() {
|
|
60
|
+
const provider = process.env.APM_PROVIDER || apm_provider_type_1.ApmProvider.OPENTELEMETRY;
|
|
61
|
+
return provider !== apm_provider_type_1.ApmProvider.ELASTIC_APM && provider !== 'elastic-apm';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Initialize OpenTelemetry SDK for standalone usage
|
|
65
|
+
*
|
|
66
|
+
* @param options Configuration options
|
|
67
|
+
* @returns The NodeSDK instance
|
|
68
|
+
*/
|
|
69
|
+
async function initOpenTelemetry(options = {}) {
|
|
70
|
+
// @ts-ignore - Optional peer dependency
|
|
71
|
+
const { NodeSDK } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-node')));
|
|
72
|
+
// @ts-ignore - Optional peer dependency
|
|
73
|
+
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/resources')));
|
|
74
|
+
// @ts-ignore - Optional peer dependency
|
|
75
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
76
|
+
console.log('[OpenTelemetry] Initializing...');
|
|
77
|
+
const serviceName = options.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
|
|
78
|
+
const environment = options.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
|
|
79
|
+
const otlpEndpoint = options.otlpEndpoint || process.env.ELASTIC_OTLP_ENDPOINT || 'http://localhost:8200/v1/traces';
|
|
80
|
+
const secretToken = options.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN;
|
|
81
|
+
const otlpAuthToken = options.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN;
|
|
82
|
+
// Build headers: merge options.otlpHeaders with Authorization if token is provided
|
|
83
|
+
const headers = { ...options.otlpHeaders };
|
|
84
|
+
const token = otlpAuthToken || secretToken;
|
|
85
|
+
if (token) {
|
|
86
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
87
|
+
}
|
|
88
|
+
console.log(`[OpenTelemetry] Service: ${serviceName}, Environment: ${environment}`);
|
|
89
|
+
console.log(`[OpenTelemetry] Endpoint: ${otlpEndpoint}`);
|
|
90
|
+
// Build exporters array
|
|
91
|
+
const exporters = [];
|
|
92
|
+
// OTLP Exporter with detailed logging
|
|
93
|
+
const otlpExporterBase = new OTLPTraceExporter({
|
|
94
|
+
url: otlpEndpoint,
|
|
95
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
96
|
+
});
|
|
97
|
+
// Wrap OTLP exporter - only log errors
|
|
98
|
+
const otlpExporter = {
|
|
99
|
+
export: (spans, resultCallback) => {
|
|
100
|
+
otlpExporterBase.export(spans, (result) => {
|
|
101
|
+
if (result.error) {
|
|
102
|
+
console.error(`[OpenTelemetry] Export failed: ${result.error instanceof Error ? result.error.message : result.error}`);
|
|
103
|
+
}
|
|
104
|
+
resultCallback(result);
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
shutdown: async () => {
|
|
108
|
+
await otlpExporterBase.shutdown();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
exporters.push(otlpExporter);
|
|
112
|
+
// Console Exporter for debugging
|
|
113
|
+
if (options.enableConsoleExporter ?? process.env.ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER === 'true') {
|
|
114
|
+
// @ts-ignore - Optional peer dependency
|
|
115
|
+
const { ConsoleSpanExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-trace-base')));
|
|
116
|
+
exporters.push(new ConsoleSpanExporter());
|
|
117
|
+
}
|
|
118
|
+
// Build instrumentations array
|
|
119
|
+
const instrumentations = [];
|
|
120
|
+
if (options.enableHttpInstrumentation !== false) {
|
|
121
|
+
// @ts-ignore - Optional peer dependency
|
|
122
|
+
const { HttpInstrumentation } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/instrumentation-http')));
|
|
123
|
+
instrumentations.push(new HttpInstrumentation());
|
|
124
|
+
}
|
|
125
|
+
if (options.enableExpressInstrumentation !== false) {
|
|
126
|
+
// @ts-ignore - Optional peer dependency
|
|
127
|
+
const { ExpressInstrumentation } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/instrumentation-express')));
|
|
128
|
+
instrumentations.push(new ExpressInstrumentation());
|
|
129
|
+
}
|
|
130
|
+
// Resource attributes
|
|
131
|
+
const sdkResource = resourceFromAttributes({
|
|
132
|
+
'service.name': serviceName,
|
|
133
|
+
'deployment.environment': environment,
|
|
134
|
+
'service.version': options.serviceVersion || process.env.npm_package_version || '1.0.0',
|
|
135
|
+
'service.instance.id': `${process.pid}`,
|
|
136
|
+
'host.name': require('os').hostname(),
|
|
137
|
+
});
|
|
138
|
+
// Initialize SDK
|
|
139
|
+
const sdk = new NodeSDK({
|
|
140
|
+
serviceName,
|
|
141
|
+
traceExporter: exporters.length === 1 ? exporters[0] : exporters, // Handle single/multiple exporters
|
|
142
|
+
instrumentations,
|
|
143
|
+
resource: sdkResource,
|
|
144
|
+
});
|
|
145
|
+
// Start SDK
|
|
146
|
+
sdk.start();
|
|
147
|
+
console.log('[OpenTelemetry] Initialized');
|
|
148
|
+
// Setup graceful shutdown handlers
|
|
149
|
+
const shutdown = async () => {
|
|
150
|
+
await sdk.shutdown();
|
|
151
|
+
process.exit(0);
|
|
152
|
+
};
|
|
153
|
+
process.on('SIGTERM', shutdown);
|
|
154
|
+
process.on('SIGINT', shutdown);
|
|
155
|
+
return sdk;
|
|
156
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface chung cho APM Tracing Provider
|
|
3
|
+
* Cho phép switch giữa OpenTelemetry và Elastic APM thông qua DI
|
|
4
|
+
*/
|
|
5
|
+
export interface ITracingProvider {
|
|
6
|
+
/**
|
|
7
|
+
* Bắt đầu một span mới để trace operation
|
|
8
|
+
* @param name Tên của span
|
|
9
|
+
* @param attributes Các attributes metadata
|
|
10
|
+
* @param spanKind Loại span (INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER)
|
|
11
|
+
* @returns Span object - cần gọi end() khi hoàn thành
|
|
12
|
+
*/
|
|
13
|
+
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): any;
|
|
14
|
+
/**
|
|
15
|
+
* Thực thi function với context tracing (auto-close span)
|
|
16
|
+
* @param name Tên của span
|
|
17
|
+
* @param fn Function cần trace
|
|
18
|
+
* @param attributes Các attributes metadata
|
|
19
|
+
* @returns Kết quả của function
|
|
20
|
+
*/
|
|
21
|
+
startSpanWithParent<T>(name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Capture error vào active span hiện tại
|
|
24
|
+
* @param error Error object
|
|
25
|
+
*/
|
|
26
|
+
captureError(error: Error): void;
|
|
27
|
+
/**
|
|
28
|
+
* Set attribute cho active span hiện tại
|
|
29
|
+
* @param key Tên attribute
|
|
30
|
+
* @param value Giá trị attribute
|
|
31
|
+
*/
|
|
32
|
+
setAttribute(key: string, value: string | number): void;
|
|
33
|
+
/**
|
|
34
|
+
* Kết thúc span manually
|
|
35
|
+
* @param span Span cần end (nếu không truyền, sẽ end active span)
|
|
36
|
+
*/
|
|
37
|
+
endSpan(span?: any): void;
|
|
38
|
+
/**
|
|
39
|
+
* Flush và shutdown provider (cho graceful shutdown)
|
|
40
|
+
*/
|
|
41
|
+
shutdown(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=tracing-provider.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing-provider.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/tracing-provider.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IAE9F;;;;;;OAMG;IACH,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE9H;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAEjC;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAExD;;;OAGG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IAE1B;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { ApmProvider } from '../types/apm-provider.type';
|
|
3
|
+
/**
|
|
4
|
+
* Options for configuring TracingModule
|
|
5
|
+
*/
|
|
6
|
+
export interface TracingModuleOptions {
|
|
7
|
+
/** Provider type: 'elastic-apm' or 'opentelemetry' (default: from env or 'opentelemetry') */
|
|
8
|
+
provider?: ApmProvider | string;
|
|
9
|
+
/** Service name for APM */
|
|
10
|
+
serviceName?: string;
|
|
11
|
+
/** Deployment environment */
|
|
12
|
+
environment?: string;
|
|
13
|
+
/** Elastic APM server URL (for elastic-apm provider) */
|
|
14
|
+
serverUrl?: string;
|
|
15
|
+
/** Secret token for authentication */
|
|
16
|
+
secretToken?: string;
|
|
17
|
+
/** OTLP endpoint URL (for opentelemetry provider) */
|
|
18
|
+
otlpEndpoint?: string;
|
|
19
|
+
/** Service version */
|
|
20
|
+
serviceVersion?: string;
|
|
21
|
+
/** Auto-initialize the provider on module init */
|
|
22
|
+
autoInit?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* TracingModule - Module cung cấp APM Tracing functionality cho NestJS
|
|
26
|
+
*
|
|
27
|
+
* Sử dụng decorator @Global để TracingService có thể được inject ở mọi nơi
|
|
28
|
+
* mà không cần import module vào từng module con.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Trong app.module.ts - Basic usage (reads from environment)
|
|
32
|
+
* import { TracingModule } from '@vtvlive/interactive-apm';
|
|
33
|
+
*
|
|
34
|
+
* @Module({
|
|
35
|
+
* imports: [
|
|
36
|
+
* TracingModule.registerAsync(),
|
|
37
|
+
* // ... other modules
|
|
38
|
+
* ],
|
|
39
|
+
* })
|
|
40
|
+
* export class AppModule {}
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Trong app.module.ts - With explicit options
|
|
44
|
+
* @Module({
|
|
45
|
+
* imports: [
|
|
46
|
+
* TracingModule.register({
|
|
47
|
+
* provider: 'opentelemetry',
|
|
48
|
+
* serviceName: 'my-service',
|
|
49
|
+
* otlpEndpoint: 'http://localhost:8200/v1/traces',
|
|
50
|
+
* }),
|
|
51
|
+
* // ... other modules
|
|
52
|
+
* ],
|
|
53
|
+
* })
|
|
54
|
+
* export class AppModule {}
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Trong controller/service
|
|
58
|
+
* constructor(private readonly tracingService: TracingService) {}
|
|
59
|
+
*
|
|
60
|
+
* async someMethod() {
|
|
61
|
+
* const span = this.tracingService.startSpan('operation', {}, 'SERVER');
|
|
62
|
+
* try {
|
|
63
|
+
* // ... code
|
|
64
|
+
* } finally {
|
|
65
|
+
* span.end();
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
export declare class TracingModule {
|
|
70
|
+
/**
|
|
71
|
+
* Register module with synchronous options
|
|
72
|
+
*/
|
|
73
|
+
static register(options?: TracingModuleOptions): DynamicModule;
|
|
74
|
+
/**
|
|
75
|
+
* Register module with asynchronous options (using ConfigService)
|
|
76
|
+
*/
|
|
77
|
+
static registerAsync(options?: TracingModuleAsyncOptions): DynamicModule;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Async options for TracingModule
|
|
81
|
+
*/
|
|
82
|
+
export interface TracingModuleAsyncOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Optional list of modules to import for the factory to use
|
|
85
|
+
*/
|
|
86
|
+
imports?: any[];
|
|
87
|
+
/**
|
|
88
|
+
* Factory function that returns the module options
|
|
89
|
+
*/
|
|
90
|
+
useFactory?: (...args: any[]) => Promise<TracingModuleOptions> | TracingModuleOptions;
|
|
91
|
+
/**
|
|
92
|
+
* Optional list of providers to inject into the factory
|
|
93
|
+
*/
|
|
94
|
+
inject?: any[];
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=tracing.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.module.d.ts","sourceRoot":"","sources":["../../src/modules/tracing.module.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAEa,aAAa;IACxB;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa;IAoBlE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,GAAE,yBAA8B,GAAG,aAAa;CAgC7E;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAEhB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;IAEtF;;OAEG;IACH,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;CAChB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
3
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
4
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
5
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
6
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7
|
+
var _, done = false;
|
|
8
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
9
|
+
var context = {};
|
|
10
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
11
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
12
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
13
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
14
|
+
if (kind === "accessor") {
|
|
15
|
+
if (result === void 0) continue;
|
|
16
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
17
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
18
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
19
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
20
|
+
}
|
|
21
|
+
else if (_ = accept(result)) {
|
|
22
|
+
if (kind === "field") initializers.unshift(_);
|
|
23
|
+
else descriptor[key] = _;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
27
|
+
done = true;
|
|
28
|
+
};
|
|
29
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
30
|
+
var useValue = arguments.length > 2;
|
|
31
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
32
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
33
|
+
}
|
|
34
|
+
return useValue ? value : void 0;
|
|
35
|
+
};
|
|
36
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
37
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
38
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.TracingModule = void 0;
|
|
42
|
+
// @ts-ignore - Optional peer dependency
|
|
43
|
+
const common_1 = require("@nestjs/common");
|
|
44
|
+
const tracing_service_1 = require("../services/tracing.service");
|
|
45
|
+
const tracing_provider_factory_1 = require("../factories/tracing-provider.factory");
|
|
46
|
+
/**
|
|
47
|
+
* TracingModule - Module cung cấp APM Tracing functionality cho NestJS
|
|
48
|
+
*
|
|
49
|
+
* Sử dụng decorator @Global để TracingService có thể được inject ở mọi nơi
|
|
50
|
+
* mà không cần import module vào từng module con.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Trong app.module.ts - Basic usage (reads from environment)
|
|
54
|
+
* import { TracingModule } from '@vtvlive/interactive-apm';
|
|
55
|
+
*
|
|
56
|
+
* @Module({
|
|
57
|
+
* imports: [
|
|
58
|
+
* TracingModule.registerAsync(),
|
|
59
|
+
* // ... other modules
|
|
60
|
+
* ],
|
|
61
|
+
* })
|
|
62
|
+
* export class AppModule {}
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Trong app.module.ts - With explicit options
|
|
66
|
+
* @Module({
|
|
67
|
+
* imports: [
|
|
68
|
+
* TracingModule.register({
|
|
69
|
+
* provider: 'opentelemetry',
|
|
70
|
+
* serviceName: 'my-service',
|
|
71
|
+
* otlpEndpoint: 'http://localhost:8200/v1/traces',
|
|
72
|
+
* }),
|
|
73
|
+
* // ... other modules
|
|
74
|
+
* ],
|
|
75
|
+
* })
|
|
76
|
+
* export class AppModule {}
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* // Trong controller/service
|
|
80
|
+
* constructor(private readonly tracingService: TracingService) {}
|
|
81
|
+
*
|
|
82
|
+
* async someMethod() {
|
|
83
|
+
* const span = this.tracingService.startSpan('operation', {}, 'SERVER');
|
|
84
|
+
* try {
|
|
85
|
+
* // ... code
|
|
86
|
+
* } finally {
|
|
87
|
+
* span.end();
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
let TracingModule = (() => {
|
|
92
|
+
let _classDecorators = [(0, common_1.Global)(), (0, common_1.Module)({})];
|
|
93
|
+
let _classDescriptor;
|
|
94
|
+
let _classExtraInitializers = [];
|
|
95
|
+
let _classThis;
|
|
96
|
+
var TracingModule = _classThis = class {
|
|
97
|
+
/**
|
|
98
|
+
* Register module with synchronous options
|
|
99
|
+
*/
|
|
100
|
+
static register(options = {}) {
|
|
101
|
+
const tracingProvider = (0, tracing_provider_factory_1.createTracingProvider)(options);
|
|
102
|
+
return {
|
|
103
|
+
module: TracingModule,
|
|
104
|
+
providers: [
|
|
105
|
+
{
|
|
106
|
+
provide: tracing_provider_factory_1.TRACING_PROVIDER_TOKEN,
|
|
107
|
+
useValue: tracingProvider,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
provide: tracing_service_1.TracingService,
|
|
111
|
+
useFactory: (provider) => new tracing_service_1.TracingService(provider),
|
|
112
|
+
inject: [tracing_provider_factory_1.TRACING_PROVIDER_TOKEN],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
exports: [tracing_service_1.TracingService],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Register module with asynchronous options (using ConfigService)
|
|
120
|
+
*/
|
|
121
|
+
static registerAsync(options = {}) {
|
|
122
|
+
return {
|
|
123
|
+
module: TracingModule,
|
|
124
|
+
imports: options.imports || [],
|
|
125
|
+
providers: [
|
|
126
|
+
{
|
|
127
|
+
provide: tracing_provider_factory_1.TRACING_PROVIDER_TOKEN,
|
|
128
|
+
useFactory: async (...args) => {
|
|
129
|
+
const config = options.useFactory ? await options.useFactory(...args) : {};
|
|
130
|
+
const provider = (0, tracing_provider_factory_1.createTracingProvider)(config);
|
|
131
|
+
// Auto-initialize if configured
|
|
132
|
+
if (config.autoInit !== false) {
|
|
133
|
+
// Check if provider has initialize method (not in interface)
|
|
134
|
+
if (provider.initialize && typeof provider.initialize === 'function') {
|
|
135
|
+
await provider.initialize();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return provider;
|
|
139
|
+
},
|
|
140
|
+
inject: options.inject || [],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
provide: tracing_service_1.TracingService,
|
|
144
|
+
useFactory: (provider) => new tracing_service_1.TracingService(provider),
|
|
145
|
+
inject: [tracing_provider_factory_1.TRACING_PROVIDER_TOKEN],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
exports: [tracing_service_1.TracingService],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
__setFunctionName(_classThis, "TracingModule");
|
|
153
|
+
(() => {
|
|
154
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
155
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
156
|
+
TracingModule = _classThis = _classDescriptor.value;
|
|
157
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
158
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
159
|
+
})();
|
|
160
|
+
return TracingModule = _classThis;
|
|
161
|
+
})();
|
|
162
|
+
exports.TracingModule = TracingModule;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ITracingProvider } from '../interfaces/tracing-provider.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Elastic APM Tracing Provider Implementation
|
|
4
|
+
* Sử dụng elastic-apm-node agent để gửi traces về Elastic APM server
|
|
5
|
+
*/
|
|
6
|
+
export declare class ElasticApmTracingProvider implements ITracingProvider {
|
|
7
|
+
private apm;
|
|
8
|
+
private readonly serviceName;
|
|
9
|
+
private readonly environment;
|
|
10
|
+
constructor(config?: {
|
|
11
|
+
serviceName?: string;
|
|
12
|
+
environment?: string;
|
|
13
|
+
serverUrl?: string;
|
|
14
|
+
secretToken?: string;
|
|
15
|
+
serviceVersion?: string;
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Initialize Elastic APM agent
|
|
19
|
+
* Should be called before using the provider
|
|
20
|
+
*/
|
|
21
|
+
initialize(config?: {
|
|
22
|
+
serviceName?: string;
|
|
23
|
+
serverUrl?: string;
|
|
24
|
+
secretToken?: string;
|
|
25
|
+
environment?: string;
|
|
26
|
+
serviceVersion?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
/**
|
|
29
|
+
* Bắt đầu một span mới
|
|
30
|
+
*/
|
|
31
|
+
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): any;
|
|
32
|
+
/**
|
|
33
|
+
* Thực thi function với tracing context
|
|
34
|
+
*/
|
|
35
|
+
startSpanWithParent<T>(name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Capture error vào APM
|
|
38
|
+
*/
|
|
39
|
+
captureError(error: Error): void;
|
|
40
|
+
/**
|
|
41
|
+
* Set label cho current transaction/span
|
|
42
|
+
*/
|
|
43
|
+
setAttribute(key: string, value: string | number): void;
|
|
44
|
+
/**
|
|
45
|
+
* End span manually
|
|
46
|
+
*/
|
|
47
|
+
endSpan(span?: any): void;
|
|
48
|
+
/**
|
|
49
|
+
* Flush và shutdown gracefully
|
|
50
|
+
*/
|
|
51
|
+
shutdown(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Map string span kind to Elastic APM span type
|
|
54
|
+
*/
|
|
55
|
+
private mapSpanKindToType;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=elastic-apm.tracing-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elastic-apm.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/elastic-apm.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE5E;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAChE,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO;IAK1I;;;OAGG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA6B5I;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IAkDzG;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAmBb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAMhC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOzB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAe1B"}
|