@ar-agents/mercadopago 0.9.0 → 0.10.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/dist/otel.cjs ADDED
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ // src/otel.ts
4
+ var cachedApi = void 0;
5
+ async function loadOtelApi() {
6
+ if (cachedApi !== void 0) return cachedApi;
7
+ try {
8
+ const mod = await import(
9
+ /* @vite-ignore */
10
+ '@opentelemetry/api'
11
+ );
12
+ cachedApi = mod;
13
+ return mod;
14
+ } catch {
15
+ cachedApi = null;
16
+ return null;
17
+ }
18
+ }
19
+ function createOtelHooks(opts = {}) {
20
+ const serviceName = opts.serviceName ?? "ar-agents-mercadopago";
21
+ const version = opts.version ?? "0.10.0";
22
+ const baseAttrs = opts.attributes ?? {};
23
+ let initialized = false;
24
+ let api = null;
25
+ let durationHist = null;
26
+ let requestCounter = null;
27
+ let rateLimitGauge = null;
28
+ const ensureInit = async () => {
29
+ if (initialized) return;
30
+ initialized = true;
31
+ api = await loadOtelApi();
32
+ if (!api) return;
33
+ const meter = api.metrics.getMeter(serviceName, version);
34
+ durationHist = meter.createHistogram("mp.requests.duration", {
35
+ description: "MP API request duration",
36
+ unit: "ms"
37
+ });
38
+ requestCounter = meter.createCounter("mp.requests.count", {
39
+ description: "MP API requests count by outcome"
40
+ });
41
+ rateLimitGauge = meter.createGauge?.("mp.rate_limit.remaining", {
42
+ description: "MP-reported rate limit remaining at last response",
43
+ unit: "1"
44
+ }) ?? null;
45
+ };
46
+ return {
47
+ onCall: (event) => {
48
+ void (async () => {
49
+ await ensureInit();
50
+ if (!api) return;
51
+ const attrs = {
52
+ ...baseAttrs,
53
+ "mp.method": event.method,
54
+ "mp.path": event.path,
55
+ "mp.success": event.success,
56
+ "mp.retried": event.retried
57
+ };
58
+ if (event.httpStatus !== null) attrs["http.status_code"] = event.httpStatus;
59
+ if (event.requestId) attrs["mp.request_id"] = event.requestId;
60
+ if (event.circuitState) attrs["mp.circuit_state"] = event.circuitState;
61
+ durationHist?.record(event.durationMs, attrs);
62
+ requestCounter?.add(1, attrs);
63
+ if (event.rateLimit?.remaining !== null && event.rateLimit?.remaining !== void 0) {
64
+ rateLimitGauge?.record(event.rateLimit.remaining, {
65
+ "mp.path": event.path
66
+ });
67
+ }
68
+ const tracer = api.trace.getTracer(serviceName, version);
69
+ const span = tracer.startSpan(`mp.${event.method}.${event.path}`, {
70
+ attributes: attrs
71
+ });
72
+ if (event.success) {
73
+ span.setStatus({ code: api.SpanStatusCode.OK });
74
+ } else {
75
+ span.setStatus({
76
+ code: api.SpanStatusCode.ERROR,
77
+ message: `MP request failed (status=${event.httpStatus})`
78
+ });
79
+ }
80
+ span.end();
81
+ })();
82
+ },
83
+ traceContext: () => {
84
+ if (!api) {
85
+ if (cachedApi) api = cachedApi;
86
+ }
87
+ if (!api) return void 0;
88
+ const span = api.trace.getActiveSpan();
89
+ if (!span) return void 0;
90
+ const ctx = span.spanContext();
91
+ return {
92
+ traceId: ctx.traceId,
93
+ spanId: ctx.spanId,
94
+ traceFlags: ctx.traceFlags
95
+ };
96
+ }
97
+ };
98
+ }
99
+
100
+ exports.createOtelHooks = createOtelHooks;
101
+ //# sourceMappingURL=otel.cjs.map
102
+ //# sourceMappingURL=otel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/otel.ts"],"names":[],"mappings":"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","file":"otel.cjs","sourcesContent":["/**\n * OpenTelemetry instrumentation — drop-in tracing + metrics for the MP toolkit.\n *\n * # Why a subpath?\n *\n * `@opentelemetry/api` is an OPTIONAL peer dep. Consumers who don't use\n * OpenTelemetry don't pay the bundle cost. Consumers who DO use it import\n * from `@ar-agents/mercadopago/otel` and get instant instrumentation:\n * spans for every MP request, metrics for latency/errors/rate-limit\n * remaining, and proper context propagation to your downstream traces.\n *\n * # Setup\n *\n * 1. Install: `pnpm add @opentelemetry/api`\n * 2. Wire your tracer + meter at app boot (per OpenTelemetry standard).\n * 3. Pass the instrumented hooks to MercadoPagoClient:\n *\n * ```ts\n * import { MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import { createOtelHooks } from \"@ar-agents/mercadopago/otel\";\n *\n * const otel = createOtelHooks({ serviceName: \"billing-bot\" });\n * const client = new MercadoPagoClient({\n * accessToken: process.env.MP_ACCESS_TOKEN!,\n * onCall: otel.onCall,\n * traceContext: otel.traceContext,\n * });\n * ```\n *\n * # What gets instrumented\n *\n * - **Spans**: one span per MP request, named `mp.{method}.{path}` (e.g.,\n * `mp.GET./v1/payments/123`). Includes attributes: status code,\n * request_id, retried count, success bool, MP rate-limit remaining,\n * circuit breaker state.\n * - **Metrics**: `mp.requests.duration` histogram (ms), `mp.requests.count`\n * counter (labeled by success/method/path/status), `mp.rate_limit.remaining`\n * gauge.\n *\n * # No-op fallback\n *\n * If `@opentelemetry/api` isn't installed at runtime, the hooks degrade to\n * no-ops gracefully (without throwing) so the toolkit remains importable\n * even without OTEL configured.\n */\n\n// Types-only imports — `@opentelemetry/api` is an OPTIONAL peer dep.\n// We resolve at runtime via dynamic import; if absent, we run as no-ops.\ntype Tracer = {\n startSpan(name: string, options?: unknown): {\n setAttribute(k: string, v: unknown): void;\n setStatus(s: { code: number; message?: string }): void;\n end(): void;\n spanContext(): { traceId: string; spanId: string; traceFlags: number };\n };\n};\ntype Histogram = { record(v: number, attributes?: Record<string, unknown>): void };\ntype Counter = { add(v: number, attributes?: Record<string, unknown>): void };\ntype Gauge = { record(v: number, attributes?: Record<string, unknown>): void };\n\ninterface OtelApi {\n trace: {\n getTracer(name: string, version?: string): Tracer;\n getActiveSpan(): { spanContext(): { traceId: string; spanId: string; traceFlags: number } } | undefined;\n };\n metrics: {\n getMeter(name: string, version?: string): {\n createHistogram(name: string, opts?: { description?: string; unit?: string }): Histogram;\n createCounter(name: string, opts?: { description?: string }): Counter;\n createGauge?(name: string, opts?: { description?: string; unit?: string }): Gauge;\n };\n };\n SpanStatusCode: { OK: number; ERROR: number; UNSET: number };\n}\n\nlet cachedApi: OtelApi | null | undefined = undefined;\n\nasync function loadOtelApi(): Promise<OtelApi | null> {\n if (cachedApi !== undefined) return cachedApi;\n try {\n const mod = (await import(\n /* @vite-ignore */ \"@opentelemetry/api\"\n )) as unknown as OtelApi;\n cachedApi = mod;\n return mod;\n } catch {\n cachedApi = null;\n return null;\n }\n}\n\nexport interface OtelHooksOptions {\n /** Service name shown in trace UIs. Default \"ar-agents-mercadopago\". */\n serviceName?: string;\n /** Toolkit version (defaults to a static \"0.10.x\"). */\n version?: string;\n /**\n * Attributes added to every span/metric (e.g., environment, deployment_id).\n */\n attributes?: Record<string, string | number | boolean>;\n}\n\n/**\n * Build OpenTelemetry-aware hooks for `MercadoPagoClient`. Returns:\n *\n * - `onCall`: wires every request into traces + metrics\n * - `traceContext`: extracts active span context for traceparent propagation\n *\n * Both degrade to no-ops if `@opentelemetry/api` isn't installed.\n */\nexport function createOtelHooks(opts: OtelHooksOptions = {}): {\n onCall: (event: {\n method: string;\n path: string;\n durationMs: number;\n httpStatus: number | null;\n retried: number;\n success: boolean;\n requestId?: string | null;\n rateLimit?: { remaining: number | null; resetSeconds: number | null };\n circuitState?: \"CLOSED\" | \"OPEN\" | \"HALF_OPEN\";\n traceContext?: { traceId?: string; spanId?: string };\n }) => void;\n traceContext: () => { traceId?: string; spanId?: string; traceFlags?: number } | undefined;\n} {\n const serviceName = opts.serviceName ?? \"ar-agents-mercadopago\";\n const version = opts.version ?? \"0.10.0\";\n const baseAttrs = opts.attributes ?? {};\n\n // Lazy state — resolved on first invocation (so module load is sync + safe).\n let initialized = false;\n let api: OtelApi | null = null;\n let durationHist: Histogram | null = null;\n let requestCounter: Counter | null = null;\n let rateLimitGauge: Gauge | null = null;\n\n const ensureInit = async () => {\n if (initialized) return;\n initialized = true;\n api = await loadOtelApi();\n if (!api) return;\n const meter = api.metrics.getMeter(serviceName, version);\n durationHist = meter.createHistogram(\"mp.requests.duration\", {\n description: \"MP API request duration\",\n unit: \"ms\",\n });\n requestCounter = meter.createCounter(\"mp.requests.count\", {\n description: \"MP API requests count by outcome\",\n });\n rateLimitGauge = meter.createGauge?.(\"mp.rate_limit.remaining\", {\n description: \"MP-reported rate limit remaining at last response\",\n unit: \"1\",\n }) ?? null;\n };\n\n return {\n onCall: (event) => {\n // Don't await — fire-and-forget. Best-effort observability.\n void (async () => {\n await ensureInit();\n if (!api) return;\n const attrs: Record<string, unknown> = {\n ...baseAttrs,\n \"mp.method\": event.method,\n \"mp.path\": event.path,\n \"mp.success\": event.success,\n \"mp.retried\": event.retried,\n };\n if (event.httpStatus !== null) attrs[\"http.status_code\"] = event.httpStatus;\n if (event.requestId) attrs[\"mp.request_id\"] = event.requestId;\n if (event.circuitState) attrs[\"mp.circuit_state\"] = event.circuitState;\n\n durationHist?.record(event.durationMs, attrs);\n requestCounter?.add(1, attrs);\n if (event.rateLimit?.remaining !== null && event.rateLimit?.remaining !== undefined) {\n rateLimitGauge?.record(event.rateLimit.remaining, {\n \"mp.path\": event.path,\n });\n }\n\n // Span: emit a synthetic child span scoped to this request's duration.\n const tracer = api.trace.getTracer(serviceName, version);\n const span = tracer.startSpan(`mp.${event.method}.${event.path}`, {\n attributes: attrs,\n });\n if (event.success) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n } else {\n span.setStatus({\n code: api.SpanStatusCode.ERROR,\n message: `MP request failed (status=${event.httpStatus})`,\n });\n }\n span.end();\n })();\n },\n\n traceContext: () => {\n // Synchronous getter — must be cheap. Resolves the OTEL active span\n // context if available; returns undefined if OTEL isn't installed.\n if (!api) {\n // Try to use the cached api if loadOtelApi already ran\n if (cachedApi) api = cachedApi;\n }\n if (!api) return undefined;\n const span = api.trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n return {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n traceFlags: ctx.traceFlags,\n };\n },\n };\n}\n"]}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * OpenTelemetry instrumentation — drop-in tracing + metrics for the MP toolkit.
3
+ *
4
+ * # Why a subpath?
5
+ *
6
+ * `@opentelemetry/api` is an OPTIONAL peer dep. Consumers who don't use
7
+ * OpenTelemetry don't pay the bundle cost. Consumers who DO use it import
8
+ * from `@ar-agents/mercadopago/otel` and get instant instrumentation:
9
+ * spans for every MP request, metrics for latency/errors/rate-limit
10
+ * remaining, and proper context propagation to your downstream traces.
11
+ *
12
+ * # Setup
13
+ *
14
+ * 1. Install: `pnpm add @opentelemetry/api`
15
+ * 2. Wire your tracer + meter at app boot (per OpenTelemetry standard).
16
+ * 3. Pass the instrumented hooks to MercadoPagoClient:
17
+ *
18
+ * ```ts
19
+ * import { MercadoPagoClient } from "@ar-agents/mercadopago";
20
+ * import { createOtelHooks } from "@ar-agents/mercadopago/otel";
21
+ *
22
+ * const otel = createOtelHooks({ serviceName: "billing-bot" });
23
+ * const client = new MercadoPagoClient({
24
+ * accessToken: process.env.MP_ACCESS_TOKEN!,
25
+ * onCall: otel.onCall,
26
+ * traceContext: otel.traceContext,
27
+ * });
28
+ * ```
29
+ *
30
+ * # What gets instrumented
31
+ *
32
+ * - **Spans**: one span per MP request, named `mp.{method}.{path}` (e.g.,
33
+ * `mp.GET./v1/payments/123`). Includes attributes: status code,
34
+ * request_id, retried count, success bool, MP rate-limit remaining,
35
+ * circuit breaker state.
36
+ * - **Metrics**: `mp.requests.duration` histogram (ms), `mp.requests.count`
37
+ * counter (labeled by success/method/path/status), `mp.rate_limit.remaining`
38
+ * gauge.
39
+ *
40
+ * # No-op fallback
41
+ *
42
+ * If `@opentelemetry/api` isn't installed at runtime, the hooks degrade to
43
+ * no-ops gracefully (without throwing) so the toolkit remains importable
44
+ * even without OTEL configured.
45
+ */
46
+ interface OtelHooksOptions {
47
+ /** Service name shown in trace UIs. Default "ar-agents-mercadopago". */
48
+ serviceName?: string;
49
+ /** Toolkit version (defaults to a static "0.10.x"). */
50
+ version?: string;
51
+ /**
52
+ * Attributes added to every span/metric (e.g., environment, deployment_id).
53
+ */
54
+ attributes?: Record<string, string | number | boolean>;
55
+ }
56
+ /**
57
+ * Build OpenTelemetry-aware hooks for `MercadoPagoClient`. Returns:
58
+ *
59
+ * - `onCall`: wires every request into traces + metrics
60
+ * - `traceContext`: extracts active span context for traceparent propagation
61
+ *
62
+ * Both degrade to no-ops if `@opentelemetry/api` isn't installed.
63
+ */
64
+ declare function createOtelHooks(opts?: OtelHooksOptions): {
65
+ onCall: (event: {
66
+ method: string;
67
+ path: string;
68
+ durationMs: number;
69
+ httpStatus: number | null;
70
+ retried: number;
71
+ success: boolean;
72
+ requestId?: string | null;
73
+ rateLimit?: {
74
+ remaining: number | null;
75
+ resetSeconds: number | null;
76
+ };
77
+ circuitState?: "CLOSED" | "OPEN" | "HALF_OPEN";
78
+ traceContext?: {
79
+ traceId?: string;
80
+ spanId?: string;
81
+ };
82
+ }) => void;
83
+ traceContext: () => {
84
+ traceId?: string;
85
+ spanId?: string;
86
+ traceFlags?: number;
87
+ } | undefined;
88
+ };
89
+
90
+ export { type OtelHooksOptions, createOtelHooks };
package/dist/otel.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * OpenTelemetry instrumentation — drop-in tracing + metrics for the MP toolkit.
3
+ *
4
+ * # Why a subpath?
5
+ *
6
+ * `@opentelemetry/api` is an OPTIONAL peer dep. Consumers who don't use
7
+ * OpenTelemetry don't pay the bundle cost. Consumers who DO use it import
8
+ * from `@ar-agents/mercadopago/otel` and get instant instrumentation:
9
+ * spans for every MP request, metrics for latency/errors/rate-limit
10
+ * remaining, and proper context propagation to your downstream traces.
11
+ *
12
+ * # Setup
13
+ *
14
+ * 1. Install: `pnpm add @opentelemetry/api`
15
+ * 2. Wire your tracer + meter at app boot (per OpenTelemetry standard).
16
+ * 3. Pass the instrumented hooks to MercadoPagoClient:
17
+ *
18
+ * ```ts
19
+ * import { MercadoPagoClient } from "@ar-agents/mercadopago";
20
+ * import { createOtelHooks } from "@ar-agents/mercadopago/otel";
21
+ *
22
+ * const otel = createOtelHooks({ serviceName: "billing-bot" });
23
+ * const client = new MercadoPagoClient({
24
+ * accessToken: process.env.MP_ACCESS_TOKEN!,
25
+ * onCall: otel.onCall,
26
+ * traceContext: otel.traceContext,
27
+ * });
28
+ * ```
29
+ *
30
+ * # What gets instrumented
31
+ *
32
+ * - **Spans**: one span per MP request, named `mp.{method}.{path}` (e.g.,
33
+ * `mp.GET./v1/payments/123`). Includes attributes: status code,
34
+ * request_id, retried count, success bool, MP rate-limit remaining,
35
+ * circuit breaker state.
36
+ * - **Metrics**: `mp.requests.duration` histogram (ms), `mp.requests.count`
37
+ * counter (labeled by success/method/path/status), `mp.rate_limit.remaining`
38
+ * gauge.
39
+ *
40
+ * # No-op fallback
41
+ *
42
+ * If `@opentelemetry/api` isn't installed at runtime, the hooks degrade to
43
+ * no-ops gracefully (without throwing) so the toolkit remains importable
44
+ * even without OTEL configured.
45
+ */
46
+ interface OtelHooksOptions {
47
+ /** Service name shown in trace UIs. Default "ar-agents-mercadopago". */
48
+ serviceName?: string;
49
+ /** Toolkit version (defaults to a static "0.10.x"). */
50
+ version?: string;
51
+ /**
52
+ * Attributes added to every span/metric (e.g., environment, deployment_id).
53
+ */
54
+ attributes?: Record<string, string | number | boolean>;
55
+ }
56
+ /**
57
+ * Build OpenTelemetry-aware hooks for `MercadoPagoClient`. Returns:
58
+ *
59
+ * - `onCall`: wires every request into traces + metrics
60
+ * - `traceContext`: extracts active span context for traceparent propagation
61
+ *
62
+ * Both degrade to no-ops if `@opentelemetry/api` isn't installed.
63
+ */
64
+ declare function createOtelHooks(opts?: OtelHooksOptions): {
65
+ onCall: (event: {
66
+ method: string;
67
+ path: string;
68
+ durationMs: number;
69
+ httpStatus: number | null;
70
+ retried: number;
71
+ success: boolean;
72
+ requestId?: string | null;
73
+ rateLimit?: {
74
+ remaining: number | null;
75
+ resetSeconds: number | null;
76
+ };
77
+ circuitState?: "CLOSED" | "OPEN" | "HALF_OPEN";
78
+ traceContext?: {
79
+ traceId?: string;
80
+ spanId?: string;
81
+ };
82
+ }) => void;
83
+ traceContext: () => {
84
+ traceId?: string;
85
+ spanId?: string;
86
+ traceFlags?: number;
87
+ } | undefined;
88
+ };
89
+
90
+ export { type OtelHooksOptions, createOtelHooks };
package/dist/otel.js ADDED
@@ -0,0 +1,100 @@
1
+ // src/otel.ts
2
+ var cachedApi = void 0;
3
+ async function loadOtelApi() {
4
+ if (cachedApi !== void 0) return cachedApi;
5
+ try {
6
+ const mod = await import(
7
+ /* @vite-ignore */
8
+ '@opentelemetry/api'
9
+ );
10
+ cachedApi = mod;
11
+ return mod;
12
+ } catch {
13
+ cachedApi = null;
14
+ return null;
15
+ }
16
+ }
17
+ function createOtelHooks(opts = {}) {
18
+ const serviceName = opts.serviceName ?? "ar-agents-mercadopago";
19
+ const version = opts.version ?? "0.10.0";
20
+ const baseAttrs = opts.attributes ?? {};
21
+ let initialized = false;
22
+ let api = null;
23
+ let durationHist = null;
24
+ let requestCounter = null;
25
+ let rateLimitGauge = null;
26
+ const ensureInit = async () => {
27
+ if (initialized) return;
28
+ initialized = true;
29
+ api = await loadOtelApi();
30
+ if (!api) return;
31
+ const meter = api.metrics.getMeter(serviceName, version);
32
+ durationHist = meter.createHistogram("mp.requests.duration", {
33
+ description: "MP API request duration",
34
+ unit: "ms"
35
+ });
36
+ requestCounter = meter.createCounter("mp.requests.count", {
37
+ description: "MP API requests count by outcome"
38
+ });
39
+ rateLimitGauge = meter.createGauge?.("mp.rate_limit.remaining", {
40
+ description: "MP-reported rate limit remaining at last response",
41
+ unit: "1"
42
+ }) ?? null;
43
+ };
44
+ return {
45
+ onCall: (event) => {
46
+ void (async () => {
47
+ await ensureInit();
48
+ if (!api) return;
49
+ const attrs = {
50
+ ...baseAttrs,
51
+ "mp.method": event.method,
52
+ "mp.path": event.path,
53
+ "mp.success": event.success,
54
+ "mp.retried": event.retried
55
+ };
56
+ if (event.httpStatus !== null) attrs["http.status_code"] = event.httpStatus;
57
+ if (event.requestId) attrs["mp.request_id"] = event.requestId;
58
+ if (event.circuitState) attrs["mp.circuit_state"] = event.circuitState;
59
+ durationHist?.record(event.durationMs, attrs);
60
+ requestCounter?.add(1, attrs);
61
+ if (event.rateLimit?.remaining !== null && event.rateLimit?.remaining !== void 0) {
62
+ rateLimitGauge?.record(event.rateLimit.remaining, {
63
+ "mp.path": event.path
64
+ });
65
+ }
66
+ const tracer = api.trace.getTracer(serviceName, version);
67
+ const span = tracer.startSpan(`mp.${event.method}.${event.path}`, {
68
+ attributes: attrs
69
+ });
70
+ if (event.success) {
71
+ span.setStatus({ code: api.SpanStatusCode.OK });
72
+ } else {
73
+ span.setStatus({
74
+ code: api.SpanStatusCode.ERROR,
75
+ message: `MP request failed (status=${event.httpStatus})`
76
+ });
77
+ }
78
+ span.end();
79
+ })();
80
+ },
81
+ traceContext: () => {
82
+ if (!api) {
83
+ if (cachedApi) api = cachedApi;
84
+ }
85
+ if (!api) return void 0;
86
+ const span = api.trace.getActiveSpan();
87
+ if (!span) return void 0;
88
+ const ctx = span.spanContext();
89
+ return {
90
+ traceId: ctx.traceId,
91
+ spanId: ctx.spanId,
92
+ traceFlags: ctx.traceFlags
93
+ };
94
+ }
95
+ };
96
+ }
97
+
98
+ export { createOtelHooks };
99
+ //# sourceMappingURL=otel.js.map
100
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/otel.ts"],"names":[],"mappings":"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","file":"otel.js","sourcesContent":["/**\n * OpenTelemetry instrumentation — drop-in tracing + metrics for the MP toolkit.\n *\n * # Why a subpath?\n *\n * `@opentelemetry/api` is an OPTIONAL peer dep. Consumers who don't use\n * OpenTelemetry don't pay the bundle cost. Consumers who DO use it import\n * from `@ar-agents/mercadopago/otel` and get instant instrumentation:\n * spans for every MP request, metrics for latency/errors/rate-limit\n * remaining, and proper context propagation to your downstream traces.\n *\n * # Setup\n *\n * 1. Install: `pnpm add @opentelemetry/api`\n * 2. Wire your tracer + meter at app boot (per OpenTelemetry standard).\n * 3. Pass the instrumented hooks to MercadoPagoClient:\n *\n * ```ts\n * import { MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import { createOtelHooks } from \"@ar-agents/mercadopago/otel\";\n *\n * const otel = createOtelHooks({ serviceName: \"billing-bot\" });\n * const client = new MercadoPagoClient({\n * accessToken: process.env.MP_ACCESS_TOKEN!,\n * onCall: otel.onCall,\n * traceContext: otel.traceContext,\n * });\n * ```\n *\n * # What gets instrumented\n *\n * - **Spans**: one span per MP request, named `mp.{method}.{path}` (e.g.,\n * `mp.GET./v1/payments/123`). Includes attributes: status code,\n * request_id, retried count, success bool, MP rate-limit remaining,\n * circuit breaker state.\n * - **Metrics**: `mp.requests.duration` histogram (ms), `mp.requests.count`\n * counter (labeled by success/method/path/status), `mp.rate_limit.remaining`\n * gauge.\n *\n * # No-op fallback\n *\n * If `@opentelemetry/api` isn't installed at runtime, the hooks degrade to\n * no-ops gracefully (without throwing) so the toolkit remains importable\n * even without OTEL configured.\n */\n\n// Types-only imports — `@opentelemetry/api` is an OPTIONAL peer dep.\n// We resolve at runtime via dynamic import; if absent, we run as no-ops.\ntype Tracer = {\n startSpan(name: string, options?: unknown): {\n setAttribute(k: string, v: unknown): void;\n setStatus(s: { code: number; message?: string }): void;\n end(): void;\n spanContext(): { traceId: string; spanId: string; traceFlags: number };\n };\n};\ntype Histogram = { record(v: number, attributes?: Record<string, unknown>): void };\ntype Counter = { add(v: number, attributes?: Record<string, unknown>): void };\ntype Gauge = { record(v: number, attributes?: Record<string, unknown>): void };\n\ninterface OtelApi {\n trace: {\n getTracer(name: string, version?: string): Tracer;\n getActiveSpan(): { spanContext(): { traceId: string; spanId: string; traceFlags: number } } | undefined;\n };\n metrics: {\n getMeter(name: string, version?: string): {\n createHistogram(name: string, opts?: { description?: string; unit?: string }): Histogram;\n createCounter(name: string, opts?: { description?: string }): Counter;\n createGauge?(name: string, opts?: { description?: string; unit?: string }): Gauge;\n };\n };\n SpanStatusCode: { OK: number; ERROR: number; UNSET: number };\n}\n\nlet cachedApi: OtelApi | null | undefined = undefined;\n\nasync function loadOtelApi(): Promise<OtelApi | null> {\n if (cachedApi !== undefined) return cachedApi;\n try {\n const mod = (await import(\n /* @vite-ignore */ \"@opentelemetry/api\"\n )) as unknown as OtelApi;\n cachedApi = mod;\n return mod;\n } catch {\n cachedApi = null;\n return null;\n }\n}\n\nexport interface OtelHooksOptions {\n /** Service name shown in trace UIs. Default \"ar-agents-mercadopago\". */\n serviceName?: string;\n /** Toolkit version (defaults to a static \"0.10.x\"). */\n version?: string;\n /**\n * Attributes added to every span/metric (e.g., environment, deployment_id).\n */\n attributes?: Record<string, string | number | boolean>;\n}\n\n/**\n * Build OpenTelemetry-aware hooks for `MercadoPagoClient`. Returns:\n *\n * - `onCall`: wires every request into traces + metrics\n * - `traceContext`: extracts active span context for traceparent propagation\n *\n * Both degrade to no-ops if `@opentelemetry/api` isn't installed.\n */\nexport function createOtelHooks(opts: OtelHooksOptions = {}): {\n onCall: (event: {\n method: string;\n path: string;\n durationMs: number;\n httpStatus: number | null;\n retried: number;\n success: boolean;\n requestId?: string | null;\n rateLimit?: { remaining: number | null; resetSeconds: number | null };\n circuitState?: \"CLOSED\" | \"OPEN\" | \"HALF_OPEN\";\n traceContext?: { traceId?: string; spanId?: string };\n }) => void;\n traceContext: () => { traceId?: string; spanId?: string; traceFlags?: number } | undefined;\n} {\n const serviceName = opts.serviceName ?? \"ar-agents-mercadopago\";\n const version = opts.version ?? \"0.10.0\";\n const baseAttrs = opts.attributes ?? {};\n\n // Lazy state — resolved on first invocation (so module load is sync + safe).\n let initialized = false;\n let api: OtelApi | null = null;\n let durationHist: Histogram | null = null;\n let requestCounter: Counter | null = null;\n let rateLimitGauge: Gauge | null = null;\n\n const ensureInit = async () => {\n if (initialized) return;\n initialized = true;\n api = await loadOtelApi();\n if (!api) return;\n const meter = api.metrics.getMeter(serviceName, version);\n durationHist = meter.createHistogram(\"mp.requests.duration\", {\n description: \"MP API request duration\",\n unit: \"ms\",\n });\n requestCounter = meter.createCounter(\"mp.requests.count\", {\n description: \"MP API requests count by outcome\",\n });\n rateLimitGauge = meter.createGauge?.(\"mp.rate_limit.remaining\", {\n description: \"MP-reported rate limit remaining at last response\",\n unit: \"1\",\n }) ?? null;\n };\n\n return {\n onCall: (event) => {\n // Don't await — fire-and-forget. Best-effort observability.\n void (async () => {\n await ensureInit();\n if (!api) return;\n const attrs: Record<string, unknown> = {\n ...baseAttrs,\n \"mp.method\": event.method,\n \"mp.path\": event.path,\n \"mp.success\": event.success,\n \"mp.retried\": event.retried,\n };\n if (event.httpStatus !== null) attrs[\"http.status_code\"] = event.httpStatus;\n if (event.requestId) attrs[\"mp.request_id\"] = event.requestId;\n if (event.circuitState) attrs[\"mp.circuit_state\"] = event.circuitState;\n\n durationHist?.record(event.durationMs, attrs);\n requestCounter?.add(1, attrs);\n if (event.rateLimit?.remaining !== null && event.rateLimit?.remaining !== undefined) {\n rateLimitGauge?.record(event.rateLimit.remaining, {\n \"mp.path\": event.path,\n });\n }\n\n // Span: emit a synthetic child span scoped to this request's duration.\n const tracer = api.trace.getTracer(serviceName, version);\n const span = tracer.startSpan(`mp.${event.method}.${event.path}`, {\n attributes: attrs,\n });\n if (event.success) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n } else {\n span.setStatus({\n code: api.SpanStatusCode.ERROR,\n message: `MP request failed (status=${event.httpStatus})`,\n });\n }\n span.end();\n })();\n },\n\n traceContext: () => {\n // Synchronous getter — must be cheap. Resolves the OTEL active span\n // context if available; returns undefined if OTEL isn't installed.\n if (!api) {\n // Try to use the cached api if loadOtelApi already ran\n if (cachedApi) api = cachedApi;\n }\n if (!api) return undefined;\n const span = api.trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n return {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n traceFlags: ctx.traceFlags,\n };\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar-agents/mercadopago",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The most complete Mercado Pago agent toolkit for Vercel AI SDK 6+. 82 tools, Edge Runtime, Vercel KV adapters, circuit breaker + deadline propagation + W3C trace context, HMAC webhook verify with replay protection, OAuth Marketplace, Order Management, Point Devices, marketplace splits, status_detail explainer in Spanish, AR-specific knowledge baked in. Property-tested + integration-tested vs MP sandbox + benchmarked.",
5
5
  "keywords": [
6
6
  "mercadopago",
@@ -30,6 +30,9 @@
30
30
  "*": {
31
31
  "vercel-kv": [
32
32
  "./dist/vercel-kv.d.ts"
33
+ ],
34
+ "otel": [
35
+ "./dist/otel.d.ts"
33
36
  ]
34
37
  }
35
38
  },
@@ -53,6 +56,16 @@
53
56
  "types": "./dist/vercel-kv.d.cts",
54
57
  "default": "./dist/vercel-kv.cjs"
55
58
  }
59
+ },
60
+ "./otel": {
61
+ "import": {
62
+ "types": "./dist/otel.d.ts",
63
+ "default": "./dist/otel.js"
64
+ },
65
+ "require": {
66
+ "types": "./dist/otel.d.cts",
67
+ "default": "./dist/otel.cjs"
68
+ }
56
69
  }
57
70
  },
58
71
  "files": [
@@ -98,17 +111,22 @@
98
111
  }
99
112
  ],
100
113
  "peerDependencies": {
114
+ "@opentelemetry/api": ">=1.0.0",
101
115
  "@vercel/kv": ">=2.0.0",
102
116
  "ai": ">=6.0.0",
103
117
  "zod": ">=3.0.0"
104
118
  },
105
119
  "peerDependenciesMeta": {
120
+ "@opentelemetry/api": {
121
+ "optional": true
122
+ },
106
123
  "@vercel/kv": {
107
124
  "optional": true
108
125
  }
109
126
  },
110
127
  "devDependencies": {
111
128
  "@fast-check/vitest": "^0.4.1",
129
+ "@opentelemetry/api": "^1.9.1",
112
130
  "@types/node": "^20.19.39",
113
131
  "@types/qrcode": "^1.5.6",
114
132
  "@vercel/kv": "^3.0.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://github.com/ar-agents/ar-agents/blob/main/tools-manifest.schema.json",
3
3
  "package": "@ar-agents/mercadopago",
4
- "version": "0.9.0",
4
+ "version": "0.10.0",
5
5
  "factory": "mercadoPagoTools",
6
6
  "tools": [
7
7
  {