chargebee 3.27.0 → 3.28.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/CHANGELOG.md +8 -2
- package/README.md +92 -0
- package/cjs/RequestWrapper.js +103 -12
- package/cjs/chargebee.cjs.js +2 -0
- package/cjs/createChargebee.js +18 -2
- package/cjs/environment.js +1 -1
- package/cjs/telemetry/TelemetryAdapter.js +145 -0
- package/cjs/telemetry/index.js +28 -0
- package/cjs/telemetry/otel.js +66 -0
- package/cjs/telemetry/types.js +47 -0
- package/esm/RequestWrapper.js +103 -12
- package/esm/chargebee.esm.js +1 -0
- package/esm/createChargebee.js +18 -2
- package/esm/environment.js +1 -1
- package/esm/telemetry/TelemetryAdapter.js +132 -0
- package/esm/telemetry/index.js +8 -0
- package/esm/telemetry/otel.js +62 -0
- package/esm/telemetry/types.js +44 -0
- package/package.json +40 -24
- package/types/index.d.ts +69 -0
- package/types/telemetry/otel.d.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
### v3.
|
|
1
|
+
### v3.28.0 (2026-06-30)
|
|
2
2
|
* * *
|
|
3
|
+
|
|
4
|
+
### New Features
|
|
5
|
+
* Added an optional `telemetryAdapter` config hook for tracing Chargebee API calls via OpenTelemetry (or any APM). When unconfigured, the SDK skips all telemetry work — no behavior change for existing integrations.
|
|
6
|
+
* Each API call emits one CLIENT span (`chargebee.{resource}.{operation}`) with OpenTelemetry HTTP semantic-convention attributes plus `chargebee.*` attributes, and injects W3C trace context (`traceparent`) into outbound requests for distributed tracing.
|
|
7
|
+
* Exposed `TelemetryAdapter`, `RequestTelemetryContext`, `RequestTelemetryResult`, `RequestTelemetryError`, `RequestTelemetryHandle` types and the `TelemetryAttributeKeys` constant from the CJS and ESM entry points.
|
|
8
|
+
* Added a ready-to-use OpenTelemetry adapter via the `chargebee/telemetry/otel` subpath. `@opentelemetry/api` is an optional peer dependency and is not bundled — install it in your app to use this adapter.
|
|
9
|
+
|
|
3
10
|
### Enhancements:
|
|
4
11
|
- **Zod-backed request validation** — Set `enableValidation: true` in the client configuration to validate outgoing request parameters against each endpoint's generated Zod schema before the API call is sent. Invalid payloads raise `ChargebeeZodValidationError`, which carries the offending `actionName` and the original `ZodError` (including `issues` and `flatten()`) for inspection.
|
|
5
12
|
- **Runtime dependency** — Added [`zod`](https://www.npmjs.com/package/zod) (v4) as a runtime dependency to support request validation.
|
|
@@ -41,7 +48,6 @@
|
|
|
41
48
|
- `updated_at` and `created_at` have been added as new values to enum query parameter `sort_by.desc` in [`list_omnichannel_subscriptions`](https://apidocs.chargebee.com/docs/api/omnichannel_subscriptions/list-omnichannel-subscriptions) of [`OmnichannelSubscription`](https://apidocs.chargebee.com/docs/api/omnichannel_subscriptions).
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
|
|
45
51
|
### v3.25.0 (2026-06-08)
|
|
46
52
|
* * *
|
|
47
53
|
### New Attributes:
|
package/README.md
CHANGED
|
@@ -602,6 +602,98 @@ const chargebee = new Chargebee({
|
|
|
602
602
|
|
|
603
603
|
These examples demonstrate how to implement and inject custom clients using `axios` and `ky`, respectively.
|
|
604
604
|
|
|
605
|
+
### Telemetry (OpenTelemetry)
|
|
606
|
+
|
|
607
|
+
Optional. Pass a `telemetryAdapter` when you want Chargebee API calls traced in your observability stack (Datadog, Splunk, Honeycomb, Jaeger, etc.). The SDK ships a ready-to-use OpenTelemetry adapter, so for most setups you only need to add `@opentelemetry/api` and wire the adapter on the client.
|
|
608
|
+
|
|
609
|
+
`@opentelemetry/api` is an **optional peer dependency** — it is not bundled with `chargebee` and is only loaded when you import the adapter, so a plain `import 'chargebee'` stays dependency-free.
|
|
610
|
+
|
|
611
|
+
The SDK builds standardized span attributes (`ctx.startAttributes`, `result.endAttributes`) following the stable [OpenTelemetry HTTP semantic conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/) (`url.full`, `http.request.method`, `http.response.status_code`, `server.address`, `error.type`) plus Chargebee-specific `chargebee.*` attributes — use them as-is so spans render correctly in your APM and stay consistent across SDKs.
|
|
612
|
+
|
|
613
|
+
Spans are named `chargebee.{resource}.{operation}` (e.g. `chargebee.subscription.create`).
|
|
614
|
+
|
|
615
|
+
#### Quick start (built-in adapter)
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
npm install chargebee @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Configure OpenTelemetry once at app startup. Exporting (endpoint, service name, credentials) is driven entirely by your OpenTelemetry runtime and the standard `OTEL_*` environment variables — the adapter just uses the globally registered tracer:
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// instrumentation.ts — node --require ./instrumentation.js app.js
|
|
625
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
626
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
627
|
+
|
|
628
|
+
new NodeSDK({
|
|
629
|
+
serviceName: process.env.OTEL_SERVICE_NAME ?? 'billing-service',
|
|
630
|
+
traceExporter: new OTLPTraceExporter({
|
|
631
|
+
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ?? 'http://localhost:4318/v1/traces',
|
|
632
|
+
}),
|
|
633
|
+
}).start();
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Then import the ready-to-use adapter and pass it on client initialization:
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
import Chargebee from 'chargebee';
|
|
640
|
+
import otelDefaultAdapter from 'chargebee/telemetry/otel';
|
|
641
|
+
|
|
642
|
+
const chargebee = new Chargebee({
|
|
643
|
+
site: '{{site}}',
|
|
644
|
+
apiKey: '{{api-key}}',
|
|
645
|
+
telemetryAdapter: otelDefaultAdapter,
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
That's it — every SDK call now emits a `chargebee.<resource>.<operation>` CLIENT span that attaches to the active OpenTelemetry context (or starts a root span), with W3C trace context propagated to Chargebee. Spans are exported by your own OpenTelemetry setup, so they flow to whatever backend you've configured (Datadog, Splunk, Honeycomb, Jaeger, etc.) — refer to your APM vendor's OpenTelemetry/OTLP documentation for exporter endpoints.
|
|
650
|
+
|
|
651
|
+
#### Custom adapter (advanced)
|
|
652
|
+
|
|
653
|
+
To customize behavior (different tracer name, extra attributes, a non-OpenTelemetry backend, etc.), implement `TelemetryAdapter` yourself instead of importing the built-in adapter:
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import Chargebee, {
|
|
657
|
+
type TelemetryAdapter,
|
|
658
|
+
type RequestTelemetryContext,
|
|
659
|
+
type RequestTelemetryResult,
|
|
660
|
+
} from 'chargebee';
|
|
661
|
+
import { context, propagation, trace, SpanKind, SpanStatusCode, type Span } from '@opentelemetry/api';
|
|
662
|
+
|
|
663
|
+
class OtelTelemetryAdapter implements TelemetryAdapter<Span> {
|
|
664
|
+
private readonly tracer = trace.getTracer('chargebee-node');
|
|
665
|
+
|
|
666
|
+
onRequestStart(ctx: RequestTelemetryContext, requestHeaders: Record<string, string | number>): Span {
|
|
667
|
+
const span = this.tracer.startSpan(ctx.spanName, {
|
|
668
|
+
kind: SpanKind.CLIENT,
|
|
669
|
+
attributes: ctx.startAttributes,
|
|
670
|
+
});
|
|
671
|
+
propagation.inject(trace.setSpan(context.active(), span), requestHeaders);
|
|
672
|
+
return span;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
onRequestEnd(span: Span | void, result: RequestTelemetryResult) {
|
|
676
|
+
if (!span) return;
|
|
677
|
+
for (const [key, value] of Object.entries(result.endAttributes)) {
|
|
678
|
+
span.setAttribute(key, value);
|
|
679
|
+
}
|
|
680
|
+
if (result.error) {
|
|
681
|
+
span.recordException(new Error(result.error.message));
|
|
682
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error.message });
|
|
683
|
+
} else {
|
|
684
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
685
|
+
}
|
|
686
|
+
span.end();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const chargebee = new Chargebee({
|
|
691
|
+
site: '{{site}}',
|
|
692
|
+
apiKey: '{{api-key}}',
|
|
693
|
+
telemetryAdapter: new OtelTelemetryAdapter(),
|
|
694
|
+
});
|
|
695
|
+
```
|
|
696
|
+
|
|
605
697
|
## Feedback
|
|
606
698
|
|
|
607
699
|
If you find any bugs or have any questions / feedback, open an issue in this repository or reach out to us on dx@chargebee.com
|
package/cjs/RequestWrapper.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RequestWrapper = void 0;
|
|
4
4
|
const util_js_1 = require("./util.js");
|
|
5
|
+
const index_js_1 = require("./telemetry/index.js");
|
|
5
6
|
const coreCommon_js_1 = require("./coreCommon.js");
|
|
6
7
|
const node_buffer_1 = require("node:buffer");
|
|
7
8
|
const chargebeeZodValidationError_js_1 = require("./chargebeeZodValidationError.js");
|
|
@@ -45,9 +46,28 @@ class RequestWrapper {
|
|
|
45
46
|
}
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
49
|
+
_buildRequestUrl(env, urlIdParam, params) {
|
|
50
|
+
let path = (0, util_js_1.getApiURL)(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
|
|
51
|
+
if (this.apiCall.httpMethod === 'GET') {
|
|
52
|
+
let requestParams = params;
|
|
53
|
+
if (typeof requestParams === 'undefined' || requestParams === null) {
|
|
54
|
+
requestParams = {};
|
|
55
|
+
}
|
|
56
|
+
const queryParam = this.apiCall.isListReq
|
|
57
|
+
? (0, util_js_1.encodeListParams)((0, util_js_1.serialize)(requestParams))
|
|
58
|
+
: (0, util_js_1.encodeParams)((0, util_js_1.serialize)(requestParams));
|
|
59
|
+
path += '?' + queryParam;
|
|
60
|
+
}
|
|
61
|
+
return new URL(path, `${env.protocol}://${(0, util_js_1.getHost)(env, this.apiCall.subDomain)}${env.port ? `:${env.port}` : ''}`);
|
|
62
|
+
}
|
|
48
63
|
async request() {
|
|
49
64
|
let _env = {};
|
|
50
65
|
(0, util_js_1.extend)(true, _env, this.envArg);
|
|
66
|
+
// Class-based adapters (e.g. OpenTelemetry) keep methods on the prototype;
|
|
67
|
+
// deep extend only copies own enumerable properties, so preserve by reference.
|
|
68
|
+
if (this.envArg.telemetryAdapter !== undefined) {
|
|
69
|
+
_env.telemetryAdapter = this.envArg.telemetryAdapter;
|
|
70
|
+
}
|
|
51
71
|
const env = _env;
|
|
52
72
|
const retryConfig = Object.assign({ enabled: false, maxRetries: 3, delayMs: 200, retryOn: [500, 502, 503, 504] }, env.retryConfig);
|
|
53
73
|
const urlIdParam = this.apiCall.hasIdInUrl ? this.args[0] : null;
|
|
@@ -77,19 +97,43 @@ class RequestWrapper {
|
|
|
77
97
|
retryConfig.enabled) {
|
|
78
98
|
this.httpHeaders['chargebee-idempotency-key'] = (0, util_js_1.generateUUIDv4)();
|
|
79
99
|
}
|
|
100
|
+
const telemetryAdapter = env.telemetryAdapter;
|
|
101
|
+
const telemetryHeaders = {};
|
|
102
|
+
const requestStartTime = Date.now();
|
|
103
|
+
const requestUrl = this._buildRequestUrl(env, urlIdParam, params);
|
|
104
|
+
// No telemetry adapter configured => skip all telemetry work (zero overhead).
|
|
105
|
+
let telemetryHandle;
|
|
106
|
+
if (telemetryAdapter !== undefined) {
|
|
107
|
+
const telemetryContext = (0, index_js_1.buildRequestTelemetryContext)({
|
|
108
|
+
resource: this.apiCall.resource,
|
|
109
|
+
operation: this.apiCall.methodName,
|
|
110
|
+
httpMethod: this.apiCall.httpMethod,
|
|
111
|
+
httpUrl: `${requestUrl.origin}${requestUrl.pathname}`,
|
|
112
|
+
serverAddress: requestUrl.hostname,
|
|
113
|
+
chargebeeSite: env.site,
|
|
114
|
+
chargebeeApiVersion: (0, index_js_1.resolveChargebeeApiVersion)(env.apiPath),
|
|
115
|
+
sdkVersion: env.clientVersion,
|
|
116
|
+
requestHeaders: this.httpHeaders,
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
telemetryHandle = telemetryAdapter.onRequestStart(telemetryContext, telemetryHeaders);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const message = err instanceof Error
|
|
123
|
+
? err.message
|
|
124
|
+
: 'Unknown telemetry adapter error';
|
|
125
|
+
(0, util_js_1.log)(env, {
|
|
126
|
+
level: 'ERROR',
|
|
127
|
+
message: `Telemetry adapter onRequestStart failed: ${message}. Continuing without telemetry.`,
|
|
128
|
+
});
|
|
129
|
+
telemetryHandle = undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
80
132
|
const makeRequest = async (attempt = 0) => {
|
|
81
|
-
let path = (0, util_js_1.getApiURL)(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
|
|
82
133
|
let requestParams = params;
|
|
83
134
|
if (typeof requestParams === 'undefined' || requestParams === null) {
|
|
84
135
|
requestParams = {};
|
|
85
136
|
}
|
|
86
|
-
if (this.apiCall.httpMethod === 'GET') {
|
|
87
|
-
const queryParam = this.apiCall.isListReq
|
|
88
|
-
? (0, util_js_1.encodeListParams)((0, util_js_1.serialize)(requestParams))
|
|
89
|
-
: (0, util_js_1.encodeParams)((0, util_js_1.serialize)(requestParams));
|
|
90
|
-
path += '?' + queryParam;
|
|
91
|
-
requestParams = {};
|
|
92
|
-
}
|
|
93
137
|
const jsonKeys = this.apiCall.jsonKeys;
|
|
94
138
|
let data = null;
|
|
95
139
|
if (this.apiCall.httpMethod !== 'GET') {
|
|
@@ -97,7 +141,7 @@ class RequestWrapper {
|
|
|
97
141
|
? JSON.stringify(requestParams)
|
|
98
142
|
: (0, util_js_1.encodeParams)(requestParams, undefined, undefined, undefined, jsonKeys);
|
|
99
143
|
}
|
|
100
|
-
const requestHeaders = Object.assign({}, this.httpHeaders);
|
|
144
|
+
const requestHeaders = Object.assign(Object.assign({}, this.httpHeaders), telemetryHeaders);
|
|
101
145
|
if (data && data.length) {
|
|
102
146
|
(0, util_js_1.extend)(true, requestHeaders, {
|
|
103
147
|
'Content-Length': node_buffer_1.Buffer.byteLength(data, 'utf8'),
|
|
@@ -117,8 +161,7 @@ class RequestWrapper {
|
|
|
117
161
|
if (attempt > 0) {
|
|
118
162
|
requestHeaders['X-CB-Retry-Attempt'] = attempt.toString();
|
|
119
163
|
}
|
|
120
|
-
const
|
|
121
|
-
const request = new Request(url, {
|
|
164
|
+
const request = new Request(requestUrl, {
|
|
122
165
|
method: this.apiCall.httpMethod,
|
|
123
166
|
body: data || undefined,
|
|
124
167
|
headers: this._createHeaders(requestHeaders),
|
|
@@ -180,7 +223,55 @@ class RequestWrapper {
|
|
|
180
223
|
return await withRetry(retryCount + 1, startTime);
|
|
181
224
|
}
|
|
182
225
|
};
|
|
183
|
-
const
|
|
226
|
+
const runWithTelemetry = async (adapter) => {
|
|
227
|
+
var _a;
|
|
228
|
+
try {
|
|
229
|
+
const result = await withRetry(0, requestStartTime);
|
|
230
|
+
const httpStatusCode = typeof (result === null || result === void 0 ? void 0 : result.httpStatusCode) === 'number'
|
|
231
|
+
? result.httpStatusCode
|
|
232
|
+
: 200;
|
|
233
|
+
try {
|
|
234
|
+
adapter.onRequestEnd(telemetryHandle, (0, index_js_1.buildRequestTelemetryResult)({
|
|
235
|
+
httpStatusCode,
|
|
236
|
+
durationMs: Date.now() - requestStartTime,
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const message = err instanceof Error
|
|
241
|
+
? err.message
|
|
242
|
+
: 'Unknown telemetry adapter error';
|
|
243
|
+
(0, util_js_1.log)(env, {
|
|
244
|
+
level: 'ERROR',
|
|
245
|
+
message: `Telemetry adapter onRequestEnd failed: ${message}.`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
const httpStatusCode = (_a = (0, index_js_1.extractHttpStatusCode)(err)) !== null && _a !== void 0 ? _a : 500;
|
|
252
|
+
const telemetryError = (0, index_js_1.extractRequestTelemetryError)(err);
|
|
253
|
+
try {
|
|
254
|
+
adapter.onRequestEnd(telemetryHandle, (0, index_js_1.buildRequestTelemetryResult)({
|
|
255
|
+
httpStatusCode,
|
|
256
|
+
durationMs: Date.now() - requestStartTime,
|
|
257
|
+
error: telemetryError,
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
catch (telemetryErr) {
|
|
261
|
+
const message = telemetryErr instanceof Error
|
|
262
|
+
? telemetryErr.message
|
|
263
|
+
: 'Unknown telemetry adapter error';
|
|
264
|
+
(0, util_js_1.log)(env, {
|
|
265
|
+
level: 'ERROR',
|
|
266
|
+
message: `Telemetry adapter onRequestEnd failed: ${message}.`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const promise = telemetryAdapter !== undefined
|
|
273
|
+
? runWithTelemetry(telemetryAdapter)
|
|
274
|
+
: withRetry(0, requestStartTime);
|
|
184
275
|
return (0, util_js_1.callbackifyPromise)(promise);
|
|
185
276
|
}
|
|
186
277
|
_createHeaders(httpHeaders) {
|
package/cjs/chargebee.cjs.js
CHANGED
|
@@ -4,6 +4,7 @@ const createChargebee_js_1 = require("./createChargebee.js");
|
|
|
4
4
|
const FetchClient_js_1 = require("./net/FetchClient.js");
|
|
5
5
|
const handler_js_1 = require("./resources/webhook/handler.js");
|
|
6
6
|
const auth_js_1 = require("./resources/webhook/auth.js");
|
|
7
|
+
const index_js_1 = require("./telemetry/index.js");
|
|
7
8
|
const chargebeeZodValidationError_js_1 = require("./chargebeeZodValidationError.js");
|
|
8
9
|
const httpClient = new FetchClient_js_1.FetchHttpClient();
|
|
9
10
|
const Chargebee = (0, createChargebee_js_1.CreateChargebee)(httpClient);
|
|
@@ -19,5 +20,6 @@ module.exports.WebhookError = handler_js_1.WebhookError;
|
|
|
19
20
|
module.exports.WebhookAuthenticationError = handler_js_1.WebhookAuthenticationError;
|
|
20
21
|
module.exports.WebhookPayloadValidationError = handler_js_1.WebhookPayloadValidationError;
|
|
21
22
|
module.exports.WebhookPayloadParseError = handler_js_1.WebhookPayloadParseError;
|
|
23
|
+
module.exports.TelemetryAttributeKeys = index_js_1.TelemetryAttributeKeys;
|
|
22
24
|
// Export validation error class
|
|
23
25
|
module.exports.ChargebeeZodValidationError = chargebeeZodValidationError_js_1.ChargebeeZodValidationError;
|
package/cjs/createChargebee.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
2
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
14
|
exports.CreateChargebee = void 0;
|
|
4
15
|
const RequestWrapper_js_1 = require("./RequestWrapper.js");
|
|
@@ -10,10 +21,14 @@ const handler_js_1 = require("./resources/webhook/handler.js");
|
|
|
10
21
|
const CreateChargebee = (httpClient) => {
|
|
11
22
|
const Chargebee = function (conf) {
|
|
12
23
|
this._env = Object.assign({}, environment_js_1.Environment);
|
|
13
|
-
|
|
24
|
+
const { telemetryAdapter, httpClient: configHttpClient } = conf, confToMerge = __rest(conf, ["telemetryAdapter", "httpClient"]);
|
|
25
|
+
(0, util_js_1.extend)(true, this._env, confToMerge);
|
|
14
26
|
// @ts-ignore
|
|
15
27
|
this._env.httpClient =
|
|
16
|
-
|
|
28
|
+
configHttpClient != null ? configHttpClient : httpClient;
|
|
29
|
+
if (telemetryAdapter !== undefined) {
|
|
30
|
+
this._env.telemetryAdapter = telemetryAdapter;
|
|
31
|
+
}
|
|
17
32
|
this._buildResources();
|
|
18
33
|
this._endpoints = api_endpoints_js_1.Endpoints;
|
|
19
34
|
// Initialize webhooks handler with auto-configured Basic Auth (if env vars are set)
|
|
@@ -85,6 +100,7 @@ const CreateChargebee = (httpClient) => {
|
|
|
85
100
|
for (let apiIdx = 0; apiIdx < apiCalls.length; apiIdx++) {
|
|
86
101
|
const metaArr = apiCalls[apiIdx];
|
|
87
102
|
const apiCall = {
|
|
103
|
+
resource: res,
|
|
88
104
|
methodName: metaArr[0],
|
|
89
105
|
httpMethod: metaArr[1],
|
|
90
106
|
urlPrefix: metaArr[2],
|
package/cjs/environment.js
CHANGED
|
@@ -11,7 +11,7 @@ exports.Environment = {
|
|
|
11
11
|
hostSuffix: '.chargebee.com',
|
|
12
12
|
apiPath: '/api/v2',
|
|
13
13
|
timeout: DEFAULT_TIME_OUT,
|
|
14
|
-
clientVersion: 'v3.
|
|
14
|
+
clientVersion: 'v3.28.0',
|
|
15
15
|
port: DEFAULT_PORT,
|
|
16
16
|
timemachineWaitInMillis: DEFAULT_TIME_MACHINE_WAIT,
|
|
17
17
|
exportWaitInMillis: DEFAULT_EXPORT_WAIT,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This file is auto-generated by Chargebee.
|
|
4
|
+
* For more information on how to make changes to this file, please see the README.
|
|
5
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
6
|
+
* Copyright 2026 Chargebee Inc.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.NO_OP_TELEMETRY_ADAPTER = exports.NoOpTelemetryAdapter = void 0;
|
|
10
|
+
exports.buildSpanName = buildSpanName;
|
|
11
|
+
exports.resolveChargebeeApiVersion = resolveChargebeeApiVersion;
|
|
12
|
+
exports.buildRequestHeaderSpanAttributes = buildRequestHeaderSpanAttributes;
|
|
13
|
+
exports.buildRequestStartSpanAttributes = buildRequestStartSpanAttributes;
|
|
14
|
+
exports.buildRequestEndSpanAttributes = buildRequestEndSpanAttributes;
|
|
15
|
+
exports.buildRequestTelemetryContext = buildRequestTelemetryContext;
|
|
16
|
+
exports.buildRequestTelemetryResult = buildRequestTelemetryResult;
|
|
17
|
+
exports.extractRequestTelemetryError = extractRequestTelemetryError;
|
|
18
|
+
exports.extractHttpStatusCode = extractHttpStatusCode;
|
|
19
|
+
const types_js_1 = require("./types.js");
|
|
20
|
+
class NoOpTelemetryAdapter {
|
|
21
|
+
onRequestStart() {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
onRequestEnd() {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.NoOpTelemetryAdapter = NoOpTelemetryAdapter;
|
|
29
|
+
exports.NO_OP_TELEMETRY_ADAPTER = new NoOpTelemetryAdapter();
|
|
30
|
+
function buildSpanName(resource, operation) {
|
|
31
|
+
return `${types_js_1.TELEMETRY_SPAN_NAME_PREFIX}.${resource}.${operation}`;
|
|
32
|
+
}
|
|
33
|
+
function resolveChargebeeApiVersion(apiPath) {
|
|
34
|
+
return apiPath === '/api/v1' ? 'v1' : 'v2';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Captures Chargebee custom request headers as OTel span attributes.
|
|
38
|
+
*
|
|
39
|
+
* Headers whose (lowercased) name starts with `chargebee-` are recorded as
|
|
40
|
+
* `http.request.header.<name>` with string[] values, per the OpenTelemetry HTTP semantic
|
|
41
|
+
* conventions. The `chargebee-request-origin-*` family (origin IP, email, device) is skipped
|
|
42
|
+
* because it carries end-user PII. Matching by prefix means new `chargebee-*` headers are
|
|
43
|
+
* captured automatically without an SDK upgrade.
|
|
44
|
+
*/
|
|
45
|
+
function buildRequestHeaderSpanAttributes(requestHeaders) {
|
|
46
|
+
const attributes = {};
|
|
47
|
+
if (!requestHeaders) {
|
|
48
|
+
return attributes;
|
|
49
|
+
}
|
|
50
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
51
|
+
if (value === undefined || value === null) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const lowerName = name.toLowerCase();
|
|
55
|
+
if (!lowerName.startsWith(types_js_1.CHARGEBEE_TELEMETRY_HEADER_PREFIX) ||
|
|
56
|
+
lowerName.startsWith(types_js_1.CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
attributes[`${types_js_1.HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX}${lowerName}`] = [
|
|
60
|
+
String(value),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
return attributes;
|
|
64
|
+
}
|
|
65
|
+
function buildRequestStartSpanAttributes(input) {
|
|
66
|
+
return Object.assign({ [types_js_1.TelemetryAttributeKeys.URL_FULL]: input.httpUrl, [types_js_1.TelemetryAttributeKeys.HTTP_REQUEST_METHOD]: input.httpMethod, [types_js_1.TelemetryAttributeKeys.SERVER_ADDRESS]: input.serverAddress, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_SITE]: input.chargebeeSite, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_API_VERSION]: input.chargebeeApiVersion, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_RESOURCE]: input.resource, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_OPERATION]: input.operation, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_SDK_NAME]: types_js_1.CHARGEBEE_SDK_NAME, [types_js_1.TelemetryAttributeKeys.CHARGEBEE_SDK_VERSION]: input.sdkVersion }, buildRequestHeaderSpanAttributes(input.requestHeaders));
|
|
67
|
+
}
|
|
68
|
+
function buildRequestEndSpanAttributes(result) {
|
|
69
|
+
const attributes = {
|
|
70
|
+
[types_js_1.TelemetryAttributeKeys.HTTP_RESPONSE_STATUS_CODE]: result.httpStatusCode,
|
|
71
|
+
};
|
|
72
|
+
if (result.error) {
|
|
73
|
+
// error.type is the status code on failed requests
|
|
74
|
+
attributes[types_js_1.TelemetryAttributeKeys.ERROR_TYPE] = String(result.httpStatusCode);
|
|
75
|
+
if (result.error.chargebeeErrorCode) {
|
|
76
|
+
attributes[types_js_1.TelemetryAttributeKeys.CHARGEBEE_ERROR_CODE] =
|
|
77
|
+
result.error.chargebeeErrorCode;
|
|
78
|
+
}
|
|
79
|
+
if (result.error.chargebeeApiErrorType) {
|
|
80
|
+
attributes[types_js_1.TelemetryAttributeKeys.CHARGEBEE_ERROR_TYPE] =
|
|
81
|
+
result.error.chargebeeApiErrorType;
|
|
82
|
+
}
|
|
83
|
+
if (result.error.chargebeeErrorParam) {
|
|
84
|
+
attributes[types_js_1.TelemetryAttributeKeys.CHARGEBEE_ERROR_PARAM] =
|
|
85
|
+
result.error.chargebeeErrorParam;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return attributes;
|
|
89
|
+
}
|
|
90
|
+
function buildRequestTelemetryContext(input) {
|
|
91
|
+
return {
|
|
92
|
+
spanName: buildSpanName(input.resource, input.operation),
|
|
93
|
+
resource: input.resource,
|
|
94
|
+
operation: input.operation,
|
|
95
|
+
httpMethod: input.httpMethod,
|
|
96
|
+
httpUrl: input.httpUrl,
|
|
97
|
+
serverAddress: input.serverAddress,
|
|
98
|
+
chargebeeSite: input.chargebeeSite,
|
|
99
|
+
chargebeeApiVersion: input.chargebeeApiVersion,
|
|
100
|
+
sdkName: types_js_1.CHARGEBEE_SDK_NAME,
|
|
101
|
+
sdkVersion: input.sdkVersion,
|
|
102
|
+
startAttributes: buildRequestStartSpanAttributes(input),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function buildRequestTelemetryResult(result) {
|
|
106
|
+
return Object.assign(Object.assign({}, result), { endAttributes: buildRequestEndSpanAttributes(result) });
|
|
107
|
+
}
|
|
108
|
+
function extractRequestTelemetryError(err) {
|
|
109
|
+
if (err == null || typeof err !== 'object') {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const errorObj = err;
|
|
113
|
+
const message = typeof errorObj.message === 'string'
|
|
114
|
+
? errorObj.message
|
|
115
|
+
: 'Chargebee API request failed';
|
|
116
|
+
const result = { message };
|
|
117
|
+
if (typeof errorObj.api_error_code === 'string') {
|
|
118
|
+
result.chargebeeErrorCode = errorObj.api_error_code;
|
|
119
|
+
}
|
|
120
|
+
if (typeof errorObj.type === 'string') {
|
|
121
|
+
result.chargebeeApiErrorType = errorObj.type;
|
|
122
|
+
}
|
|
123
|
+
if (typeof errorObj.param === 'string') {
|
|
124
|
+
result.chargebeeErrorParam = errorObj.param;
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
function extractHttpStatusCode(err) {
|
|
129
|
+
if (err == null || typeof err !== 'object') {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const errorObj = err;
|
|
133
|
+
for (const key of [
|
|
134
|
+
'http_status_code',
|
|
135
|
+
'httpStatusCode',
|
|
136
|
+
'http_code',
|
|
137
|
+
'statusCode',
|
|
138
|
+
]) {
|
|
139
|
+
const value = errorObj[key];
|
|
140
|
+
if (typeof value === 'number') {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This file is auto-generated by Chargebee.
|
|
4
|
+
* For more information on how to make changes to this file, please see the README.
|
|
5
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
6
|
+
* Copyright 2026 Chargebee Inc.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.resolveChargebeeApiVersion = exports.extractRequestTelemetryError = exports.extractHttpStatusCode = exports.buildSpanName = exports.buildRequestTelemetryResult = exports.buildRequestTelemetryContext = exports.buildRequestStartSpanAttributes = exports.buildRequestHeaderSpanAttributes = exports.buildRequestEndSpanAttributes = exports.NoOpTelemetryAdapter = exports.NO_OP_TELEMETRY_ADAPTER = exports.TelemetryAttributeKeys = exports.TELEMETRY_SPAN_NAME_PREFIX = exports.HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX = exports.CHARGEBEE_TELEMETRY_HEADER_PREFIX = exports.CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX = exports.CHARGEBEE_SDK_NAME = void 0;
|
|
10
|
+
var types_js_1 = require("./types.js");
|
|
11
|
+
Object.defineProperty(exports, "CHARGEBEE_SDK_NAME", { enumerable: true, get: function () { return types_js_1.CHARGEBEE_SDK_NAME; } });
|
|
12
|
+
Object.defineProperty(exports, "CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX", { enumerable: true, get: function () { return types_js_1.CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX; } });
|
|
13
|
+
Object.defineProperty(exports, "CHARGEBEE_TELEMETRY_HEADER_PREFIX", { enumerable: true, get: function () { return types_js_1.CHARGEBEE_TELEMETRY_HEADER_PREFIX; } });
|
|
14
|
+
Object.defineProperty(exports, "HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX", { enumerable: true, get: function () { return types_js_1.HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX; } });
|
|
15
|
+
Object.defineProperty(exports, "TELEMETRY_SPAN_NAME_PREFIX", { enumerable: true, get: function () { return types_js_1.TELEMETRY_SPAN_NAME_PREFIX; } });
|
|
16
|
+
Object.defineProperty(exports, "TelemetryAttributeKeys", { enumerable: true, get: function () { return types_js_1.TelemetryAttributeKeys; } });
|
|
17
|
+
var TelemetryAdapter_js_1 = require("./TelemetryAdapter.js");
|
|
18
|
+
Object.defineProperty(exports, "NO_OP_TELEMETRY_ADAPTER", { enumerable: true, get: function () { return TelemetryAdapter_js_1.NO_OP_TELEMETRY_ADAPTER; } });
|
|
19
|
+
Object.defineProperty(exports, "NoOpTelemetryAdapter", { enumerable: true, get: function () { return TelemetryAdapter_js_1.NoOpTelemetryAdapter; } });
|
|
20
|
+
Object.defineProperty(exports, "buildRequestEndSpanAttributes", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildRequestEndSpanAttributes; } });
|
|
21
|
+
Object.defineProperty(exports, "buildRequestHeaderSpanAttributes", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildRequestHeaderSpanAttributes; } });
|
|
22
|
+
Object.defineProperty(exports, "buildRequestStartSpanAttributes", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildRequestStartSpanAttributes; } });
|
|
23
|
+
Object.defineProperty(exports, "buildRequestTelemetryContext", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildRequestTelemetryContext; } });
|
|
24
|
+
Object.defineProperty(exports, "buildRequestTelemetryResult", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildRequestTelemetryResult; } });
|
|
25
|
+
Object.defineProperty(exports, "buildSpanName", { enumerable: true, get: function () { return TelemetryAdapter_js_1.buildSpanName; } });
|
|
26
|
+
Object.defineProperty(exports, "extractHttpStatusCode", { enumerable: true, get: function () { return TelemetryAdapter_js_1.extractHttpStatusCode; } });
|
|
27
|
+
Object.defineProperty(exports, "extractRequestTelemetryError", { enumerable: true, get: function () { return TelemetryAdapter_js_1.extractRequestTelemetryError; } });
|
|
28
|
+
Object.defineProperty(exports, "resolveChargebeeApiVersion", { enumerable: true, get: function () { return TelemetryAdapter_js_1.resolveChargebeeApiVersion; } });
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This file is auto-generated by Chargebee.
|
|
4
|
+
* For more information on how to make changes to this file, please see the README.
|
|
5
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
6
|
+
* Copyright 2026 Chargebee Inc.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.OtelTelemetryAdapter = void 0;
|
|
10
|
+
const api_1 = require("@opentelemetry/api");
|
|
11
|
+
const types_js_1 = require("./types.js");
|
|
12
|
+
const tracer = api_1.trace.getTracer(types_js_1.CHARGEBEE_SDK_NAME);
|
|
13
|
+
function toRecordedException(error) {
|
|
14
|
+
var _a;
|
|
15
|
+
const exception = new Error(error.message);
|
|
16
|
+
exception.name = (_a = error.chargebeeErrorCode) !== null && _a !== void 0 ? _a : 'ChargebeeAPIError';
|
|
17
|
+
return exception;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ready-to-use OpenTelemetry adapter for the Chargebee SDK.
|
|
21
|
+
*
|
|
22
|
+
* Each SDK request becomes a CLIENT span attached to the active OpenTelemetry
|
|
23
|
+
* context (or a root span when none is active), and the W3C trace context is
|
|
24
|
+
* propagated to Chargebee so the trace continues server-side.
|
|
25
|
+
*
|
|
26
|
+
* Exporting (endpoint, service name, credentials) is configured by your own
|
|
27
|
+
* OpenTelemetry runtime via the standard `OTEL_*` environment variables — this
|
|
28
|
+
* adapter only uses the globally registered tracer from `@opentelemetry/api`.
|
|
29
|
+
*
|
|
30
|
+
* `@opentelemetry/api` is an optional peer dependency; install it to use this
|
|
31
|
+
* adapter. A default-exported, ready-to-use instance is provided.
|
|
32
|
+
*/
|
|
33
|
+
class OtelTelemetryAdapter {
|
|
34
|
+
onRequestStart(ctx, requestHeaders) {
|
|
35
|
+
// startSpan adopts the active context's span as parent (or starts a root span if none).
|
|
36
|
+
const span = tracer.startSpan(ctx.spanName, {
|
|
37
|
+
kind: api_1.SpanKind.CLIENT,
|
|
38
|
+
attributes: ctx.startAttributes,
|
|
39
|
+
});
|
|
40
|
+
// Propagate W3C trace context to Chargebee so the trace continues server-side.
|
|
41
|
+
api_1.propagation.inject(api_1.trace.setSpan(api_1.context.active(), span), requestHeaders);
|
|
42
|
+
return span;
|
|
43
|
+
}
|
|
44
|
+
onRequestEnd(span, result) {
|
|
45
|
+
if (!span) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const [key, value] of Object.entries(result.endAttributes)) {
|
|
49
|
+
span.setAttribute(key, value);
|
|
50
|
+
}
|
|
51
|
+
if (result.error) {
|
|
52
|
+
span.recordException(toRecordedException(result.error));
|
|
53
|
+
span.setStatus({
|
|
54
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
55
|
+
message: result.error.message,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
|
60
|
+
}
|
|
61
|
+
span.end();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.OtelTelemetryAdapter = OtelTelemetryAdapter;
|
|
65
|
+
const otelDefaultAdapter = new OtelTelemetryAdapter();
|
|
66
|
+
exports.default = otelDefaultAdapter;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This file is auto-generated by Chargebee.
|
|
4
|
+
* For more information on how to make changes to this file, please see the README.
|
|
5
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
6
|
+
* Copyright 2026 Chargebee Inc.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TelemetryAttributeKeys = exports.CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX = exports.CHARGEBEE_TELEMETRY_HEADER_PREFIX = exports.HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX = exports.TELEMETRY_SPAN_NAME_PREFIX = exports.CHARGEBEE_SDK_NAME = void 0;
|
|
10
|
+
/** SDK identifier recorded on telemetry spans. */
|
|
11
|
+
exports.CHARGEBEE_SDK_NAME = 'chargebee-node';
|
|
12
|
+
/** Standard span name prefix: chargebee.{resource}.{operation} */
|
|
13
|
+
exports.TELEMETRY_SPAN_NAME_PREFIX = 'chargebee';
|
|
14
|
+
/**
|
|
15
|
+
* OTel HTTP semantic-convention prefix for request-header attributes:
|
|
16
|
+
* `http.request.header.<lowercased-name>` with string[] values.
|
|
17
|
+
*/
|
|
18
|
+
exports.HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX = 'http.request.header.';
|
|
19
|
+
/**
|
|
20
|
+
* Request headers whose (lowercased) name starts with this prefix are captured as span
|
|
21
|
+
* attributes. Using a prefix instead of a fixed list means any future `chargebee-*` header
|
|
22
|
+
* is picked up automatically, with no SDK upgrade required.
|
|
23
|
+
*/
|
|
24
|
+
exports.CHARGEBEE_TELEMETRY_HEADER_PREFIX = 'chargebee-';
|
|
25
|
+
/**
|
|
26
|
+
* Headers under this sub-prefix carry end-user PII (origin IP, email, device) and are
|
|
27
|
+
* excluded from spans by default. Chargebee namespaces such headers under
|
|
28
|
+
* `chargebee-request-origin-*`, so future PII headers stay excluded automatically.
|
|
29
|
+
*/
|
|
30
|
+
exports.CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX = 'chargebee-request-origin-';
|
|
31
|
+
/** Span attribute keys — shared across Chargebee SDKs. */
|
|
32
|
+
exports.TelemetryAttributeKeys = {
|
|
33
|
+
URL_FULL: 'url.full',
|
|
34
|
+
HTTP_REQUEST_METHOD: 'http.request.method',
|
|
35
|
+
HTTP_RESPONSE_STATUS_CODE: 'http.response.status_code',
|
|
36
|
+
SERVER_ADDRESS: 'server.address',
|
|
37
|
+
ERROR_TYPE: 'error.type',
|
|
38
|
+
CHARGEBEE_SITE: 'chargebee.site',
|
|
39
|
+
CHARGEBEE_API_VERSION: 'chargebee.api_version',
|
|
40
|
+
CHARGEBEE_RESOURCE: 'chargebee.resource',
|
|
41
|
+
CHARGEBEE_OPERATION: 'chargebee.operation',
|
|
42
|
+
CHARGEBEE_SDK_NAME: 'chargebee.sdk.name',
|
|
43
|
+
CHARGEBEE_SDK_VERSION: 'chargebee.sdk.version',
|
|
44
|
+
CHARGEBEE_ERROR_CODE: 'chargebee.error.code',
|
|
45
|
+
CHARGEBEE_ERROR_TYPE: 'chargebee.error.type',
|
|
46
|
+
CHARGEBEE_ERROR_PARAM: 'chargebee.error.param',
|
|
47
|
+
};
|
package/esm/RequestWrapper.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extend, callbackifyPromise, getApiURL, encodeListParams, encodeParams, serialize, getHost, log, generateUUIDv4 as uuidv4, } from './util.js';
|
|
2
|
+
import { buildRequestTelemetryContext, buildRequestTelemetryResult, extractHttpStatusCode, extractRequestTelemetryError, resolveChargebeeApiVersion, } from './telemetry/index.js';
|
|
2
3
|
import { handleResponse } from './coreCommon.js';
|
|
3
4
|
import { Buffer } from 'node:buffer';
|
|
4
5
|
import { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';
|
|
@@ -42,9 +43,28 @@ export class RequestWrapper {
|
|
|
42
43
|
}
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
46
|
+
_buildRequestUrl(env, urlIdParam, params) {
|
|
47
|
+
let path = getApiURL(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
|
|
48
|
+
if (this.apiCall.httpMethod === 'GET') {
|
|
49
|
+
let requestParams = params;
|
|
50
|
+
if (typeof requestParams === 'undefined' || requestParams === null) {
|
|
51
|
+
requestParams = {};
|
|
52
|
+
}
|
|
53
|
+
const queryParam = this.apiCall.isListReq
|
|
54
|
+
? encodeListParams(serialize(requestParams))
|
|
55
|
+
: encodeParams(serialize(requestParams));
|
|
56
|
+
path += '?' + queryParam;
|
|
57
|
+
}
|
|
58
|
+
return new URL(path, `${env.protocol}://${getHost(env, this.apiCall.subDomain)}${env.port ? `:${env.port}` : ''}`);
|
|
59
|
+
}
|
|
45
60
|
async request() {
|
|
46
61
|
let _env = {};
|
|
47
62
|
extend(true, _env, this.envArg);
|
|
63
|
+
// Class-based adapters (e.g. OpenTelemetry) keep methods on the prototype;
|
|
64
|
+
// deep extend only copies own enumerable properties, so preserve by reference.
|
|
65
|
+
if (this.envArg.telemetryAdapter !== undefined) {
|
|
66
|
+
_env.telemetryAdapter = this.envArg.telemetryAdapter;
|
|
67
|
+
}
|
|
48
68
|
const env = _env;
|
|
49
69
|
const retryConfig = Object.assign({ enabled: false, maxRetries: 3, delayMs: 200, retryOn: [500, 502, 503, 504] }, env.retryConfig);
|
|
50
70
|
const urlIdParam = this.apiCall.hasIdInUrl ? this.args[0] : null;
|
|
@@ -74,19 +94,43 @@ export class RequestWrapper {
|
|
|
74
94
|
retryConfig.enabled) {
|
|
75
95
|
this.httpHeaders['chargebee-idempotency-key'] = uuidv4();
|
|
76
96
|
}
|
|
97
|
+
const telemetryAdapter = env.telemetryAdapter;
|
|
98
|
+
const telemetryHeaders = {};
|
|
99
|
+
const requestStartTime = Date.now();
|
|
100
|
+
const requestUrl = this._buildRequestUrl(env, urlIdParam, params);
|
|
101
|
+
// No telemetry adapter configured => skip all telemetry work (zero overhead).
|
|
102
|
+
let telemetryHandle;
|
|
103
|
+
if (telemetryAdapter !== undefined) {
|
|
104
|
+
const telemetryContext = buildRequestTelemetryContext({
|
|
105
|
+
resource: this.apiCall.resource,
|
|
106
|
+
operation: this.apiCall.methodName,
|
|
107
|
+
httpMethod: this.apiCall.httpMethod,
|
|
108
|
+
httpUrl: `${requestUrl.origin}${requestUrl.pathname}`,
|
|
109
|
+
serverAddress: requestUrl.hostname,
|
|
110
|
+
chargebeeSite: env.site,
|
|
111
|
+
chargebeeApiVersion: resolveChargebeeApiVersion(env.apiPath),
|
|
112
|
+
sdkVersion: env.clientVersion,
|
|
113
|
+
requestHeaders: this.httpHeaders,
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
telemetryHandle = telemetryAdapter.onRequestStart(telemetryContext, telemetryHeaders);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const message = err instanceof Error
|
|
120
|
+
? err.message
|
|
121
|
+
: 'Unknown telemetry adapter error';
|
|
122
|
+
log(env, {
|
|
123
|
+
level: 'ERROR',
|
|
124
|
+
message: `Telemetry adapter onRequestStart failed: ${message}. Continuing without telemetry.`,
|
|
125
|
+
});
|
|
126
|
+
telemetryHandle = undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
77
129
|
const makeRequest = async (attempt = 0) => {
|
|
78
|
-
let path = getApiURL(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
|
|
79
130
|
let requestParams = params;
|
|
80
131
|
if (typeof requestParams === 'undefined' || requestParams === null) {
|
|
81
132
|
requestParams = {};
|
|
82
133
|
}
|
|
83
|
-
if (this.apiCall.httpMethod === 'GET') {
|
|
84
|
-
const queryParam = this.apiCall.isListReq
|
|
85
|
-
? encodeListParams(serialize(requestParams))
|
|
86
|
-
: encodeParams(serialize(requestParams));
|
|
87
|
-
path += '?' + queryParam;
|
|
88
|
-
requestParams = {};
|
|
89
|
-
}
|
|
90
134
|
const jsonKeys = this.apiCall.jsonKeys;
|
|
91
135
|
let data = null;
|
|
92
136
|
if (this.apiCall.httpMethod !== 'GET') {
|
|
@@ -94,7 +138,7 @@ export class RequestWrapper {
|
|
|
94
138
|
? JSON.stringify(requestParams)
|
|
95
139
|
: encodeParams(requestParams, undefined, undefined, undefined, jsonKeys);
|
|
96
140
|
}
|
|
97
|
-
const requestHeaders = Object.assign({}, this.httpHeaders);
|
|
141
|
+
const requestHeaders = Object.assign(Object.assign({}, this.httpHeaders), telemetryHeaders);
|
|
98
142
|
if (data && data.length) {
|
|
99
143
|
extend(true, requestHeaders, {
|
|
100
144
|
'Content-Length': Buffer.byteLength(data, 'utf8'),
|
|
@@ -114,8 +158,7 @@ export class RequestWrapper {
|
|
|
114
158
|
if (attempt > 0) {
|
|
115
159
|
requestHeaders['X-CB-Retry-Attempt'] = attempt.toString();
|
|
116
160
|
}
|
|
117
|
-
const
|
|
118
|
-
const request = new Request(url, {
|
|
161
|
+
const request = new Request(requestUrl, {
|
|
119
162
|
method: this.apiCall.httpMethod,
|
|
120
163
|
body: data || undefined,
|
|
121
164
|
headers: this._createHeaders(requestHeaders),
|
|
@@ -177,7 +220,55 @@ export class RequestWrapper {
|
|
|
177
220
|
return await withRetry(retryCount + 1, startTime);
|
|
178
221
|
}
|
|
179
222
|
};
|
|
180
|
-
const
|
|
223
|
+
const runWithTelemetry = async (adapter) => {
|
|
224
|
+
var _a;
|
|
225
|
+
try {
|
|
226
|
+
const result = await withRetry(0, requestStartTime);
|
|
227
|
+
const httpStatusCode = typeof (result === null || result === void 0 ? void 0 : result.httpStatusCode) === 'number'
|
|
228
|
+
? result.httpStatusCode
|
|
229
|
+
: 200;
|
|
230
|
+
try {
|
|
231
|
+
adapter.onRequestEnd(telemetryHandle, buildRequestTelemetryResult({
|
|
232
|
+
httpStatusCode,
|
|
233
|
+
durationMs: Date.now() - requestStartTime,
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
const message = err instanceof Error
|
|
238
|
+
? err.message
|
|
239
|
+
: 'Unknown telemetry adapter error';
|
|
240
|
+
log(env, {
|
|
241
|
+
level: 'ERROR',
|
|
242
|
+
message: `Telemetry adapter onRequestEnd failed: ${message}.`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
const httpStatusCode = (_a = extractHttpStatusCode(err)) !== null && _a !== void 0 ? _a : 500;
|
|
249
|
+
const telemetryError = extractRequestTelemetryError(err);
|
|
250
|
+
try {
|
|
251
|
+
adapter.onRequestEnd(telemetryHandle, buildRequestTelemetryResult({
|
|
252
|
+
httpStatusCode,
|
|
253
|
+
durationMs: Date.now() - requestStartTime,
|
|
254
|
+
error: telemetryError,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
catch (telemetryErr) {
|
|
258
|
+
const message = telemetryErr instanceof Error
|
|
259
|
+
? telemetryErr.message
|
|
260
|
+
: 'Unknown telemetry adapter error';
|
|
261
|
+
log(env, {
|
|
262
|
+
level: 'ERROR',
|
|
263
|
+
message: `Telemetry adapter onRequestEnd failed: ${message}.`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const promise = telemetryAdapter !== undefined
|
|
270
|
+
? runWithTelemetry(telemetryAdapter)
|
|
271
|
+
: withRetry(0, requestStartTime);
|
|
181
272
|
return callbackifyPromise(promise);
|
|
182
273
|
}
|
|
183
274
|
_createHeaders(httpHeaders) {
|
package/esm/chargebee.esm.js
CHANGED
|
@@ -7,5 +7,6 @@ export default Chargebee;
|
|
|
7
7
|
export { WebhookEventType, WebhookContentType, } from './resources/webhook/handler.js';
|
|
8
8
|
export { basicAuthValidator } from './resources/webhook/auth.js';
|
|
9
9
|
export { WebhookError, WebhookAuthenticationError, WebhookPayloadValidationError, WebhookPayloadParseError, } from './resources/webhook/handler.js';
|
|
10
|
+
export { TelemetryAttributeKeys } from './telemetry/index.js';
|
|
10
11
|
// Export validation error class
|
|
11
12
|
export { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';
|
package/esm/createChargebee.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { RequestWrapper } from './RequestWrapper.js';
|
|
2
13
|
import { Environment } from './environment.js';
|
|
3
14
|
import { Endpoints } from './resources/api_endpoints.js';
|
|
@@ -7,10 +18,14 @@ import { WebhookHandler, createDefaultHandler, } from './resources/webhook/handl
|
|
|
7
18
|
export const CreateChargebee = (httpClient) => {
|
|
8
19
|
const Chargebee = function (conf) {
|
|
9
20
|
this._env = Object.assign({}, Environment);
|
|
10
|
-
|
|
21
|
+
const { telemetryAdapter, httpClient: configHttpClient } = conf, confToMerge = __rest(conf, ["telemetryAdapter", "httpClient"]);
|
|
22
|
+
extend(true, this._env, confToMerge);
|
|
11
23
|
// @ts-ignore
|
|
12
24
|
this._env.httpClient =
|
|
13
|
-
|
|
25
|
+
configHttpClient != null ? configHttpClient : httpClient;
|
|
26
|
+
if (telemetryAdapter !== undefined) {
|
|
27
|
+
this._env.telemetryAdapter = telemetryAdapter;
|
|
28
|
+
}
|
|
14
29
|
this._buildResources();
|
|
15
30
|
this._endpoints = Endpoints;
|
|
16
31
|
// Initialize webhooks handler with auto-configured Basic Auth (if env vars are set)
|
|
@@ -82,6 +97,7 @@ export const CreateChargebee = (httpClient) => {
|
|
|
82
97
|
for (let apiIdx = 0; apiIdx < apiCalls.length; apiIdx++) {
|
|
83
98
|
const metaArr = apiCalls[apiIdx];
|
|
84
99
|
const apiCall = {
|
|
100
|
+
resource: res,
|
|
85
101
|
methodName: metaArr[0],
|
|
86
102
|
httpMethod: metaArr[1],
|
|
87
103
|
urlPrefix: metaArr[2],
|
package/esm/environment.js
CHANGED
|
@@ -8,7 +8,7 @@ export const Environment = {
|
|
|
8
8
|
hostSuffix: '.chargebee.com',
|
|
9
9
|
apiPath: '/api/v2',
|
|
10
10
|
timeout: DEFAULT_TIME_OUT,
|
|
11
|
-
clientVersion: 'v3.
|
|
11
|
+
clientVersion: 'v3.28.0',
|
|
12
12
|
port: DEFAULT_PORT,
|
|
13
13
|
timemachineWaitInMillis: DEFAULT_TIME_MACHINE_WAIT,
|
|
14
14
|
exportWaitInMillis: DEFAULT_EXPORT_WAIT,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is auto-generated by Chargebee.
|
|
3
|
+
* For more information on how to make changes to this file, please see the README.
|
|
4
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
5
|
+
* Copyright 2026 Chargebee Inc.
|
|
6
|
+
*/
|
|
7
|
+
import { CHARGEBEE_SDK_NAME, CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX, CHARGEBEE_TELEMETRY_HEADER_PREFIX, HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX, TELEMETRY_SPAN_NAME_PREFIX, TelemetryAttributeKeys, } from './types.js';
|
|
8
|
+
export class NoOpTelemetryAdapter {
|
|
9
|
+
onRequestStart() {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
onRequestEnd() {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const NO_OP_TELEMETRY_ADAPTER = new NoOpTelemetryAdapter();
|
|
17
|
+
export function buildSpanName(resource, operation) {
|
|
18
|
+
return `${TELEMETRY_SPAN_NAME_PREFIX}.${resource}.${operation}`;
|
|
19
|
+
}
|
|
20
|
+
export function resolveChargebeeApiVersion(apiPath) {
|
|
21
|
+
return apiPath === '/api/v1' ? 'v1' : 'v2';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Captures Chargebee custom request headers as OTel span attributes.
|
|
25
|
+
*
|
|
26
|
+
* Headers whose (lowercased) name starts with `chargebee-` are recorded as
|
|
27
|
+
* `http.request.header.<name>` with string[] values, per the OpenTelemetry HTTP semantic
|
|
28
|
+
* conventions. The `chargebee-request-origin-*` family (origin IP, email, device) is skipped
|
|
29
|
+
* because it carries end-user PII. Matching by prefix means new `chargebee-*` headers are
|
|
30
|
+
* captured automatically without an SDK upgrade.
|
|
31
|
+
*/
|
|
32
|
+
export function buildRequestHeaderSpanAttributes(requestHeaders) {
|
|
33
|
+
const attributes = {};
|
|
34
|
+
if (!requestHeaders) {
|
|
35
|
+
return attributes;
|
|
36
|
+
}
|
|
37
|
+
for (const [name, value] of Object.entries(requestHeaders)) {
|
|
38
|
+
if (value === undefined || value === null) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const lowerName = name.toLowerCase();
|
|
42
|
+
if (!lowerName.startsWith(CHARGEBEE_TELEMETRY_HEADER_PREFIX) ||
|
|
43
|
+
lowerName.startsWith(CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
attributes[`${HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX}${lowerName}`] = [
|
|
47
|
+
String(value),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
return attributes;
|
|
51
|
+
}
|
|
52
|
+
export function buildRequestStartSpanAttributes(input) {
|
|
53
|
+
return Object.assign({ [TelemetryAttributeKeys.URL_FULL]: input.httpUrl, [TelemetryAttributeKeys.HTTP_REQUEST_METHOD]: input.httpMethod, [TelemetryAttributeKeys.SERVER_ADDRESS]: input.serverAddress, [TelemetryAttributeKeys.CHARGEBEE_SITE]: input.chargebeeSite, [TelemetryAttributeKeys.CHARGEBEE_API_VERSION]: input.chargebeeApiVersion, [TelemetryAttributeKeys.CHARGEBEE_RESOURCE]: input.resource, [TelemetryAttributeKeys.CHARGEBEE_OPERATION]: input.operation, [TelemetryAttributeKeys.CHARGEBEE_SDK_NAME]: CHARGEBEE_SDK_NAME, [TelemetryAttributeKeys.CHARGEBEE_SDK_VERSION]: input.sdkVersion }, buildRequestHeaderSpanAttributes(input.requestHeaders));
|
|
54
|
+
}
|
|
55
|
+
export function buildRequestEndSpanAttributes(result) {
|
|
56
|
+
const attributes = {
|
|
57
|
+
[TelemetryAttributeKeys.HTTP_RESPONSE_STATUS_CODE]: result.httpStatusCode,
|
|
58
|
+
};
|
|
59
|
+
if (result.error) {
|
|
60
|
+
// error.type is the status code on failed requests
|
|
61
|
+
attributes[TelemetryAttributeKeys.ERROR_TYPE] = String(result.httpStatusCode);
|
|
62
|
+
if (result.error.chargebeeErrorCode) {
|
|
63
|
+
attributes[TelemetryAttributeKeys.CHARGEBEE_ERROR_CODE] =
|
|
64
|
+
result.error.chargebeeErrorCode;
|
|
65
|
+
}
|
|
66
|
+
if (result.error.chargebeeApiErrorType) {
|
|
67
|
+
attributes[TelemetryAttributeKeys.CHARGEBEE_ERROR_TYPE] =
|
|
68
|
+
result.error.chargebeeApiErrorType;
|
|
69
|
+
}
|
|
70
|
+
if (result.error.chargebeeErrorParam) {
|
|
71
|
+
attributes[TelemetryAttributeKeys.CHARGEBEE_ERROR_PARAM] =
|
|
72
|
+
result.error.chargebeeErrorParam;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return attributes;
|
|
76
|
+
}
|
|
77
|
+
export function buildRequestTelemetryContext(input) {
|
|
78
|
+
return {
|
|
79
|
+
spanName: buildSpanName(input.resource, input.operation),
|
|
80
|
+
resource: input.resource,
|
|
81
|
+
operation: input.operation,
|
|
82
|
+
httpMethod: input.httpMethod,
|
|
83
|
+
httpUrl: input.httpUrl,
|
|
84
|
+
serverAddress: input.serverAddress,
|
|
85
|
+
chargebeeSite: input.chargebeeSite,
|
|
86
|
+
chargebeeApiVersion: input.chargebeeApiVersion,
|
|
87
|
+
sdkName: CHARGEBEE_SDK_NAME,
|
|
88
|
+
sdkVersion: input.sdkVersion,
|
|
89
|
+
startAttributes: buildRequestStartSpanAttributes(input),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function buildRequestTelemetryResult(result) {
|
|
93
|
+
return Object.assign(Object.assign({}, result), { endAttributes: buildRequestEndSpanAttributes(result) });
|
|
94
|
+
}
|
|
95
|
+
export function extractRequestTelemetryError(err) {
|
|
96
|
+
if (err == null || typeof err !== 'object') {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const errorObj = err;
|
|
100
|
+
const message = typeof errorObj.message === 'string'
|
|
101
|
+
? errorObj.message
|
|
102
|
+
: 'Chargebee API request failed';
|
|
103
|
+
const result = { message };
|
|
104
|
+
if (typeof errorObj.api_error_code === 'string') {
|
|
105
|
+
result.chargebeeErrorCode = errorObj.api_error_code;
|
|
106
|
+
}
|
|
107
|
+
if (typeof errorObj.type === 'string') {
|
|
108
|
+
result.chargebeeApiErrorType = errorObj.type;
|
|
109
|
+
}
|
|
110
|
+
if (typeof errorObj.param === 'string') {
|
|
111
|
+
result.chargebeeErrorParam = errorObj.param;
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
export function extractHttpStatusCode(err) {
|
|
116
|
+
if (err == null || typeof err !== 'object') {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const errorObj = err;
|
|
120
|
+
for (const key of [
|
|
121
|
+
'http_status_code',
|
|
122
|
+
'httpStatusCode',
|
|
123
|
+
'http_code',
|
|
124
|
+
'statusCode',
|
|
125
|
+
]) {
|
|
126
|
+
const value = errorObj[key];
|
|
127
|
+
if (typeof value === 'number') {
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is auto-generated by Chargebee.
|
|
3
|
+
* For more information on how to make changes to this file, please see the README.
|
|
4
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
5
|
+
* Copyright 2026 Chargebee Inc.
|
|
6
|
+
*/
|
|
7
|
+
export { CHARGEBEE_SDK_NAME, CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX, CHARGEBEE_TELEMETRY_HEADER_PREFIX, HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX, TELEMETRY_SPAN_NAME_PREFIX, TelemetryAttributeKeys, } from './types.js';
|
|
8
|
+
export { NO_OP_TELEMETRY_ADAPTER, NoOpTelemetryAdapter, buildRequestEndSpanAttributes, buildRequestHeaderSpanAttributes, buildRequestStartSpanAttributes, buildRequestTelemetryContext, buildRequestTelemetryResult, buildSpanName, extractHttpStatusCode, extractRequestTelemetryError, resolveChargebeeApiVersion, } from './TelemetryAdapter.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is auto-generated by Chargebee.
|
|
3
|
+
* For more information on how to make changes to this file, please see the README.
|
|
4
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
5
|
+
* Copyright 2026 Chargebee Inc.
|
|
6
|
+
*/
|
|
7
|
+
import { context, propagation, SpanKind, SpanStatusCode, trace, } from '@opentelemetry/api';
|
|
8
|
+
import { CHARGEBEE_SDK_NAME, } from './types.js';
|
|
9
|
+
const tracer = trace.getTracer(CHARGEBEE_SDK_NAME);
|
|
10
|
+
function toRecordedException(error) {
|
|
11
|
+
var _a;
|
|
12
|
+
const exception = new Error(error.message);
|
|
13
|
+
exception.name = (_a = error.chargebeeErrorCode) !== null && _a !== void 0 ? _a : 'ChargebeeAPIError';
|
|
14
|
+
return exception;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ready-to-use OpenTelemetry adapter for the Chargebee SDK.
|
|
18
|
+
*
|
|
19
|
+
* Each SDK request becomes a CLIENT span attached to the active OpenTelemetry
|
|
20
|
+
* context (or a root span when none is active), and the W3C trace context is
|
|
21
|
+
* propagated to Chargebee so the trace continues server-side.
|
|
22
|
+
*
|
|
23
|
+
* Exporting (endpoint, service name, credentials) is configured by your own
|
|
24
|
+
* OpenTelemetry runtime via the standard `OTEL_*` environment variables — this
|
|
25
|
+
* adapter only uses the globally registered tracer from `@opentelemetry/api`.
|
|
26
|
+
*
|
|
27
|
+
* `@opentelemetry/api` is an optional peer dependency; install it to use this
|
|
28
|
+
* adapter. A default-exported, ready-to-use instance is provided.
|
|
29
|
+
*/
|
|
30
|
+
export class OtelTelemetryAdapter {
|
|
31
|
+
onRequestStart(ctx, requestHeaders) {
|
|
32
|
+
// startSpan adopts the active context's span as parent (or starts a root span if none).
|
|
33
|
+
const span = tracer.startSpan(ctx.spanName, {
|
|
34
|
+
kind: SpanKind.CLIENT,
|
|
35
|
+
attributes: ctx.startAttributes,
|
|
36
|
+
});
|
|
37
|
+
// Propagate W3C trace context to Chargebee so the trace continues server-side.
|
|
38
|
+
propagation.inject(trace.setSpan(context.active(), span), requestHeaders);
|
|
39
|
+
return span;
|
|
40
|
+
}
|
|
41
|
+
onRequestEnd(span, result) {
|
|
42
|
+
if (!span) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(result.endAttributes)) {
|
|
46
|
+
span.setAttribute(key, value);
|
|
47
|
+
}
|
|
48
|
+
if (result.error) {
|
|
49
|
+
span.recordException(toRecordedException(result.error));
|
|
50
|
+
span.setStatus({
|
|
51
|
+
code: SpanStatusCode.ERROR,
|
|
52
|
+
message: result.error.message,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
57
|
+
}
|
|
58
|
+
span.end();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const otelDefaultAdapter = new OtelTelemetryAdapter();
|
|
62
|
+
export default otelDefaultAdapter;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is auto-generated by Chargebee.
|
|
3
|
+
* For more information on how to make changes to this file, please see the README.
|
|
4
|
+
* Reach out to dx@chargebee.com for any questions.
|
|
5
|
+
* Copyright 2026 Chargebee Inc.
|
|
6
|
+
*/
|
|
7
|
+
/** SDK identifier recorded on telemetry spans. */
|
|
8
|
+
export const CHARGEBEE_SDK_NAME = 'chargebee-node';
|
|
9
|
+
/** Standard span name prefix: chargebee.{resource}.{operation} */
|
|
10
|
+
export const TELEMETRY_SPAN_NAME_PREFIX = 'chargebee';
|
|
11
|
+
/**
|
|
12
|
+
* OTel HTTP semantic-convention prefix for request-header attributes:
|
|
13
|
+
* `http.request.header.<lowercased-name>` with string[] values.
|
|
14
|
+
*/
|
|
15
|
+
export const HTTP_REQUEST_HEADER_ATTRIBUTE_PREFIX = 'http.request.header.';
|
|
16
|
+
/**
|
|
17
|
+
* Request headers whose (lowercased) name starts with this prefix are captured as span
|
|
18
|
+
* attributes. Using a prefix instead of a fixed list means any future `chargebee-*` header
|
|
19
|
+
* is picked up automatically, with no SDK upgrade required.
|
|
20
|
+
*/
|
|
21
|
+
export const CHARGEBEE_TELEMETRY_HEADER_PREFIX = 'chargebee-';
|
|
22
|
+
/**
|
|
23
|
+
* Headers under this sub-prefix carry end-user PII (origin IP, email, device) and are
|
|
24
|
+
* excluded from spans by default. Chargebee namespaces such headers under
|
|
25
|
+
* `chargebee-request-origin-*`, so future PII headers stay excluded automatically.
|
|
26
|
+
*/
|
|
27
|
+
export const CHARGEBEE_TELEMETRY_HEADER_EXCLUDE_PREFIX = 'chargebee-request-origin-';
|
|
28
|
+
/** Span attribute keys — shared across Chargebee SDKs. */
|
|
29
|
+
export const TelemetryAttributeKeys = {
|
|
30
|
+
URL_FULL: 'url.full',
|
|
31
|
+
HTTP_REQUEST_METHOD: 'http.request.method',
|
|
32
|
+
HTTP_RESPONSE_STATUS_CODE: 'http.response.status_code',
|
|
33
|
+
SERVER_ADDRESS: 'server.address',
|
|
34
|
+
ERROR_TYPE: 'error.type',
|
|
35
|
+
CHARGEBEE_SITE: 'chargebee.site',
|
|
36
|
+
CHARGEBEE_API_VERSION: 'chargebee.api_version',
|
|
37
|
+
CHARGEBEE_RESOURCE: 'chargebee.resource',
|
|
38
|
+
CHARGEBEE_OPERATION: 'chargebee.operation',
|
|
39
|
+
CHARGEBEE_SDK_NAME: 'chargebee.sdk.name',
|
|
40
|
+
CHARGEBEE_SDK_VERSION: 'chargebee.sdk.version',
|
|
41
|
+
CHARGEBEE_ERROR_CODE: 'chargebee.error.code',
|
|
42
|
+
CHARGEBEE_ERROR_TYPE: 'chargebee.error.type',
|
|
43
|
+
CHARGEBEE_ERROR_PARAM: 'chargebee.error.param',
|
|
44
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chargebee",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.28.0",
|
|
4
4
|
"description": "A library for integrating with Chargebee.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepack": "npm install && npm run build",
|
|
@@ -34,33 +34,49 @@
|
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"exports": {
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./types/index.d.ts",
|
|
39
|
+
"browser": {
|
|
40
|
+
"import": "./esm/chargebee.esm.worker.js",
|
|
41
|
+
"require": "./cjs/chargebee.cjs.worker.js"
|
|
42
|
+
},
|
|
43
|
+
"worker": {
|
|
44
|
+
"import": "./esm/chargebee.esm.worker.js",
|
|
45
|
+
"require": "./cjs/chargebee.cjs.worker.js"
|
|
46
|
+
},
|
|
47
|
+
"workerd": {
|
|
48
|
+
"import": "./esm/chargebee.esm.worker.js",
|
|
49
|
+
"require": "./cjs/chargebee.cjs.worker.js"
|
|
50
|
+
},
|
|
51
|
+
"deno": {
|
|
52
|
+
"import": "./esm/chargebee.esm.worker.js",
|
|
53
|
+
"require": "./cjs/chargebee.cjs.worker.js"
|
|
54
|
+
},
|
|
55
|
+
"bun": {
|
|
56
|
+
"import": "./esm/chargebee.esm.worker.js",
|
|
57
|
+
"require": "./cjs/chargebee.cjs.worker.js"
|
|
58
|
+
},
|
|
59
|
+
"default": {
|
|
60
|
+
"import": "./esm/chargebee.esm.js",
|
|
61
|
+
"require": "./cjs/chargebee.cjs.js"
|
|
62
|
+
}
|
|
41
63
|
},
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"bun": {
|
|
55
|
-
"import": "./esm/chargebee.esm.worker.js",
|
|
56
|
-
"require": "./cjs/chargebee.cjs.worker.js"
|
|
57
|
-
},
|
|
58
|
-
"default": {
|
|
59
|
-
"import": "./esm/chargebee.esm.js",
|
|
60
|
-
"require": "./cjs/chargebee.cjs.js"
|
|
64
|
+
"./telemetry/otel": {
|
|
65
|
+
"types": "./types/telemetry/otel.d.ts",
|
|
66
|
+
"import": "./esm/telemetry/otel.js",
|
|
67
|
+
"require": "./cjs/telemetry/otel.js"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@opentelemetry/api": "^1.9.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@opentelemetry/api": {
|
|
75
|
+
"optional": true
|
|
61
76
|
}
|
|
62
77
|
},
|
|
63
78
|
"devDependencies": {
|
|
79
|
+
"@opentelemetry/api": "^1.9.0",
|
|
64
80
|
"@types/chai": "^4.3.5",
|
|
65
81
|
"@types/mocha": "^10.0.10",
|
|
66
82
|
"@types/node": "20.12.0",
|
package/types/index.d.ts
CHANGED
|
@@ -169,6 +169,11 @@ declare module 'chargebee' {
|
|
|
169
169
|
*/
|
|
170
170
|
httpClient?: HttpClientInterface;
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* @telemetryAdapter optional telemetry adapter for observability (e.g. OpenTelemetry)
|
|
174
|
+
*/
|
|
175
|
+
telemetryAdapter?: TelemetryAdapter;
|
|
176
|
+
|
|
172
177
|
/**
|
|
173
178
|
* @enableValidation When true, every request's parameters are validated against each endpoint's generated Zod schema before the HTTP request is sent. Violations throw `ChargebeeZodValidationError` with structured Zod issues. Calls with no params argument are validated as `{}`. Required resource ids in the URL path are still checked separately.
|
|
174
179
|
*/
|
|
@@ -179,6 +184,70 @@ declare module 'chargebee' {
|
|
|
179
184
|
makeApiRequest: (request: Request, timeout: number) => Promise<Response>;
|
|
180
185
|
}
|
|
181
186
|
|
|
187
|
+
export type RequestTelemetryHandle = unknown;
|
|
188
|
+
|
|
189
|
+
export const TelemetryAttributeKeys: {
|
|
190
|
+
readonly URL_FULL: 'url.full';
|
|
191
|
+
readonly HTTP_REQUEST_METHOD: 'http.request.method';
|
|
192
|
+
readonly HTTP_RESPONSE_STATUS_CODE: 'http.response.status_code';
|
|
193
|
+
readonly SERVER_ADDRESS: 'server.address';
|
|
194
|
+
readonly ERROR_TYPE: 'error.type';
|
|
195
|
+
readonly CHARGEBEE_SITE: 'chargebee.site';
|
|
196
|
+
readonly CHARGEBEE_API_VERSION: 'chargebee.api_version';
|
|
197
|
+
readonly CHARGEBEE_RESOURCE: 'chargebee.resource';
|
|
198
|
+
readonly CHARGEBEE_OPERATION: 'chargebee.operation';
|
|
199
|
+
readonly CHARGEBEE_SDK_NAME: 'chargebee.sdk.name';
|
|
200
|
+
readonly CHARGEBEE_SDK_VERSION: 'chargebee.sdk.version';
|
|
201
|
+
readonly CHARGEBEE_ERROR_CODE: 'chargebee.error.code';
|
|
202
|
+
readonly CHARGEBEE_ERROR_TYPE: 'chargebee.error.type';
|
|
203
|
+
readonly CHARGEBEE_ERROR_PARAM: 'chargebee.error.param';
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export type RequestTelemetryContext = {
|
|
207
|
+
spanName: string;
|
|
208
|
+
resource: string;
|
|
209
|
+
operation: string;
|
|
210
|
+
httpMethod: string;
|
|
211
|
+
httpUrl: string;
|
|
212
|
+
serverAddress: string;
|
|
213
|
+
chargebeeSite: string;
|
|
214
|
+
chargebeeApiVersion: 'v1' | 'v2';
|
|
215
|
+
sdkName: string;
|
|
216
|
+
sdkVersion: string;
|
|
217
|
+
/**
|
|
218
|
+
* Prebuilt span attributes — pass these to your tracer. Captured `chargebee-*` request
|
|
219
|
+
* headers appear as `http.request.header.<name>` with string[] values per OTel semconv.
|
|
220
|
+
*/
|
|
221
|
+
startAttributes: Record<string, string | string[]>;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export type RequestTelemetryError = {
|
|
225
|
+
message: string;
|
|
226
|
+
chargebeeErrorCode?: string;
|
|
227
|
+
chargebeeApiErrorType?: string;
|
|
228
|
+
chargebeeErrorParam?: string;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export type RequestTelemetryResult = {
|
|
232
|
+
httpStatusCode: number;
|
|
233
|
+
durationMs: number;
|
|
234
|
+
error?: RequestTelemetryError;
|
|
235
|
+
/** Prebuilt span attributes — pass these to your tracer. */
|
|
236
|
+
endAttributes: Record<string, string | number>;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Optional telemetry adapter. Implement as a class or plain object — the SDK
|
|
241
|
+
* keeps it by reference (never deep-cloned). Wire OpenTelemetry or other tools here.
|
|
242
|
+
*/
|
|
243
|
+
export interface TelemetryAdapter<THandle = RequestTelemetryHandle> {
|
|
244
|
+
onRequestStart(
|
|
245
|
+
context: RequestTelemetryContext,
|
|
246
|
+
requestHeaders: Record<string, string | number>,
|
|
247
|
+
): THandle | void;
|
|
248
|
+
onRequestEnd(handle: THandle | void, result: RequestTelemetryResult): void;
|
|
249
|
+
}
|
|
250
|
+
|
|
182
251
|
export type RetryConfig = {
|
|
183
252
|
/**
|
|
184
253
|
* @enabled whether to enable retry logic, default value is false
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare module 'chargebee/telemetry/otel' {
|
|
2
|
+
import type {
|
|
3
|
+
RequestTelemetryContext,
|
|
4
|
+
RequestTelemetryResult,
|
|
5
|
+
TelemetryAdapter,
|
|
6
|
+
} from 'chargebee';
|
|
7
|
+
/**
|
|
8
|
+
* Ready-to-use OpenTelemetry adapter for the Chargebee SDK. Exporting is
|
|
9
|
+
* configured by your own OpenTelemetry runtime via the standard `OTEL_*`
|
|
10
|
+
* environment variables; this adapter only uses the globally registered
|
|
11
|
+
* tracer from `@opentelemetry/api` (an optional peer dependency).
|
|
12
|
+
*/
|
|
13
|
+
export class OtelTelemetryAdapter implements TelemetryAdapter {
|
|
14
|
+
onRequestStart(
|
|
15
|
+
ctx: RequestTelemetryContext,
|
|
16
|
+
requestHeaders: Record<string, string | number>,
|
|
17
|
+
): unknown;
|
|
18
|
+
onRequestEnd(handle: unknown, result: RequestTelemetryResult): void;
|
|
19
|
+
}
|
|
20
|
+
const otelDefaultAdapter: OtelTelemetryAdapter;
|
|
21
|
+
export default otelDefaultAdapter;
|
|
22
|
+
}
|