@use-tusk/drift-node-sdk 0.1.2 → 0.1.4

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/index.cjs CHANGED
@@ -91,7 +91,7 @@ var TdInstrumentationAbstract = class {
91
91
 
92
92
  //#endregion
93
93
  //#region package.json
94
- var version = "0.1.2";
94
+ var version = "0.1.4";
95
95
 
96
96
  //#endregion
97
97
  //#region src/version.ts
@@ -1579,10 +1579,11 @@ async function findMockResponseAsync({ mockRequestData, tuskDrift, inputValueSch
1579
1579
 
1580
1580
  //#endregion
1581
1581
  //#region src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts
1582
+ let ClientRequest;
1582
1583
  /**
1583
1584
  * Mock ClientRequest implementation for Tusk Drift HTTP replay
1584
1585
  */
1585
- var TdMockClientRequest = class extends events.EventEmitter {
1586
+ var TdMockClientRequest = class TdMockClientRequest extends events.EventEmitter {
1586
1587
  constructor(options, spanInfo, callback) {
1587
1588
  super();
1588
1589
  this.INSTRUMENTATION_NAME = "HttpInstrumentation";
@@ -1591,6 +1592,7 @@ var TdMockClientRequest = class extends events.EventEmitter {
1591
1592
  this.playbackStarted = false;
1592
1593
  this.readyToStartPlaybackOnSocketEvent = false;
1593
1594
  this.headers = {};
1595
+ TdMockClientRequest._setupPrototype();
1594
1596
  this.tuskDrift = TuskDriftCore.getInstance();
1595
1597
  this.spanInfo = spanInfo;
1596
1598
  if (!options || Object.keys(options).length === 0) throw new Error("Making a request with empty `options` is not supported in TdMockClientRequest");
@@ -1831,8 +1833,16 @@ var TdMockClientRequest = class extends events.EventEmitter {
1831
1833
  this.socket.destroy();
1832
1834
  return this;
1833
1835
  }
1836
+ static _setupPrototype() {
1837
+ if (!ClientRequest) {
1838
+ ClientRequest = require("http").ClientRequest;
1839
+ Object.setPrototypeOf(TdMockClientRequest.prototype, ClientRequest.prototype);
1840
+ }
1841
+ }
1842
+ static initialize() {
1843
+ TdMockClientRequest._setupPrototype();
1844
+ }
1834
1845
  };
1835
- Object.setPrototypeOf(TdMockClientRequest.prototype, http.ClientRequest.prototype);
1836
1846
 
1837
1847
  //#endregion
1838
1848
  //#region src/instrumentation/libraries/http/HttpReplayHooks.ts
@@ -2103,7 +2113,19 @@ var HttpTransformEngine = class {
2103
2113
  const target = span[selector];
2104
2114
  if (!target) return false;
2105
2115
  try {
2106
- return jsonpath.default.apply(target.body, jsonPath, actionFunction).length > 0;
2116
+ const body = target.body;
2117
+ if (!body) return false;
2118
+ let bodyObj;
2119
+ if (typeof body === "string") try {
2120
+ const decoded = Buffer.from(body, "base64").toString("utf8");
2121
+ bodyObj = JSON.parse(decoded);
2122
+ } catch (e) {
2123
+ return false;
2124
+ }
2125
+ else bodyObj = body;
2126
+ const nodes = jsonpath.default.apply(bodyObj, jsonPath, actionFunction);
2127
+ if (typeof body === "string" && nodes.length > 0) target.body = Buffer.from(JSON.stringify(bodyObj)).toString("base64");
2128
+ return nodes.length > 0;
2107
2129
  } catch (error) {
2108
2130
  return false;
2109
2131
  }
@@ -2111,9 +2133,8 @@ var HttpTransformEngine = class {
2111
2133
  }
2112
2134
  compileHeaderAction(headerName, actionFunction, direction) {
2113
2135
  const lowerHeader = headerName.toLowerCase();
2114
- const selector = direction === "inbound" ? "inputValue" : "outputValue";
2115
2136
  return (span) => {
2116
- const target = span[selector];
2137
+ const target = span.inputValue;
2117
2138
  if (!target?.headers) return false;
2118
2139
  let applied = false;
2119
2140
  for (const key of Object.keys(target.headers)) if (key.toLowerCase() === lowerHeader) {
@@ -2134,7 +2155,15 @@ var HttpTransformEngine = class {
2134
2155
  return (span) => {
2135
2156
  const target = span[selector];
2136
2157
  if (!target || target.body === void 0) return false;
2137
- target.body = actionFunction(typeof target.body === "string" ? target.body : JSON.stringify(target.body));
2158
+ if (direction === "outbound") if (typeof target.body === "string") try {
2159
+ const decoded = Buffer.from(target.body, "base64").toString("utf8");
2160
+ const transformed = actionFunction(decoded);
2161
+ target.body = Buffer.from(transformed).toString("base64");
2162
+ } catch (error) {
2163
+ target.body = Buffer.from(actionFunction(target.body)).toString("base64");
2164
+ }
2165
+ else target.body = Buffer.from(actionFunction(JSON.stringify(target.body))).toString("base64");
2166
+ else target.body = actionFunction(typeof target.body === "string" ? target.body : JSON.stringify(target.body));
2138
2167
  return true;
2139
2168
  };
2140
2169
  }
@@ -4960,7 +4989,7 @@ var JwksRsaInstrumentation = class extends TdInstrumentationBase {
4960
4989
  const originalExpressJwtSecret = jwksModule.expressJwtSecret;
4961
4990
  jwksModule.expressJwtSecret = function(options) {
4962
4991
  logger.debug(`[JwksRsaInstrumentation] expressJwtSecret called with options:`, options);
4963
- let modifiedOptions = { ...options };
4992
+ const modifiedOptions = { ...options };
4964
4993
  if (self$1.tuskDrift.getMode() === TuskDriftMode.REPLAY) {
4965
4994
  logger.debug(`[JwksRsaInstrumentation] REPLAY MODE - Disabling rate limiting for expressJwtSecret`);
4966
4995
  modifiedOptions.rateLimit = false;
@@ -6105,9 +6134,9 @@ var require_types$3 = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/
6105
6134
  var require_ExportResult = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/ExportResult.js": ((exports) => {
6106
6135
  Object.defineProperty(exports, "__esModule", { value: true });
6107
6136
  exports.ExportResultCode = void 0;
6108
- (function(ExportResultCode$1) {
6109
- ExportResultCode$1[ExportResultCode$1["SUCCESS"] = 0] = "SUCCESS";
6110
- ExportResultCode$1[ExportResultCode$1["FAILED"] = 1] = "FAILED";
6137
+ (function(ExportResultCode$3) {
6138
+ ExportResultCode$3[ExportResultCode$3["SUCCESS"] = 0] = "SUCCESS";
6139
+ ExportResultCode$3[ExportResultCode$3["FAILED"] = 1] = "FAILED";
6111
6140
  })(exports.ExportResultCode || (exports.ExportResultCode = {}));
6112
6141
  }) });
6113
6142
 
@@ -6949,122 +6978,23 @@ var require_src$6 = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/co
6949
6978
  }) });
6950
6979
 
6951
6980
  //#endregion
6952
- //#region src/core/tracing/TdSpanExporter.ts
6953
- var import_src$1 = /* @__PURE__ */ __toESM(require_src$6(), 1);
6954
- const DRIFT_API_PATH = "/api/drift";
6981
+ //#region src/core/tracing/SpanTransformer.ts
6982
+ var import_src$3 = /* @__PURE__ */ __toESM(require_src$6(), 1);
6955
6983
  /**
6956
- * If useRemoteExport is true, TdTraceExporter exports spans to a remote endpoint via protobuf.
6957
- * If useRemoteExport is false, TdTraceExporter stores spans organized by trace ID in separate files.
6958
- * - Each trace gets its own JSONL file: `{baseDirectory}/{timestamp}_trace_{traceId}.jsonl`.
6984
+ * Utility class for transforming OpenTelemetry spans to CleanSpanData
6959
6985
  */
6960
- var TdSpanExporter = class {
6961
- constructor(config) {
6962
- this.traceFileMap = /* @__PURE__ */ new Map();
6963
- this.baseDirectory = config.baseDirectory;
6964
- this.mode = config.mode;
6965
- this.useRemoteExport = config.useRemoteExport;
6966
- this.observableServiceId = config.observableServiceId;
6967
- this.apiKey = config.apiKey;
6968
- this.tuskBackendBaseUrl = config.tuskBackendBaseUrl;
6969
- this.environment = config.environment;
6970
- this.sdkVersion = config.sdkVersion;
6971
- this.sdkInstanceId = config.sdkInstanceId;
6972
- if (!fs.existsSync(this.baseDirectory)) fs.mkdirSync(this.baseDirectory, { recursive: true });
6973
- if (this.useRemoteExport && this.apiKey) {
6974
- const transport = new __protobuf_ts_twirp_transport.TwirpFetchTransport({
6975
- baseUrl: `${this.tuskBackendBaseUrl}${DRIFT_API_PATH}`,
6976
- meta: {
6977
- "x-api-key": this.apiKey,
6978
- "x-td-skip-instrumentation": "true"
6979
- }
6980
- });
6981
- this.spanExportClient = new __use_tusk_drift_schemas_backend_span_export_service_client.SpanExportServiceClient(transport);
6982
- }
6983
- logger.debug(`TdTraceExporter initialized - ${this.useRemoteExport ? "remote export enabled" : "local file export only"}`);
6984
- }
6985
- /**
6986
- * Export spans to trace-specific files and optionally to remote endpoint
6987
- */
6988
- export(spans, resultCallback) {
6989
- logger.debug(`TdTraceExporter.export() called with ${spans.length} span(s)`);
6990
- if (this.mode !== TuskDriftMode.RECORD) {
6991
- logger.debug(`Not recording spans in tuskDriftMode: ${this.mode}`);
6992
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
6993
- return;
6994
- }
6995
- if (this.useRemoteExport) if (this.spanExportClient) this.exportToRemote(spans).then(() => {
6996
- logger.debug(`Successfully exported ${spans.length} spans to remote endpoint`);
6997
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
6998
- }).catch((error) => {
6999
- logger.error(`Failed to export spans to remote:`, error);
7000
- resultCallback({
7001
- code: import_src$1.ExportResultCode.FAILED,
7002
- error: error instanceof Error ? error : /* @__PURE__ */ new Error("Remote export failed")
7003
- });
7004
- });
7005
- else {
7006
- logger.error("Remote export client not initialized, likely because apiKey is not provided");
7007
- resultCallback({
7008
- code: import_src$1.ExportResultCode.FAILED,
7009
- error: /* @__PURE__ */ new Error("Remote export client not initialized, likely because apiKey is not provided")
7010
- });
7011
- }
7012
- else {
7013
- this.exportToLocalFiles(spans);
7014
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
7015
- }
7016
- }
7017
- /**
7018
- * Export spans to remote endpoint via protobuf
7019
- */
7020
- async exportToRemote(spans) {
7021
- if (!this.spanExportClient) throw new Error("Remote export client not initialized");
7022
- if (!this.observableServiceId) throw new Error("Observable service ID not provided in config");
7023
- const protoSpans = spans.map((span) => this.transformSpanToProtobuf(span));
7024
- const request = {
7025
- observableServiceId: this.observableServiceId,
7026
- environment: this.environment,
7027
- sdkVersion: this.sdkVersion,
7028
- sdkInstanceId: this.sdkInstanceId,
7029
- spans: protoSpans
7030
- };
7031
- const response = await this.spanExportClient.exportSpans(request);
7032
- if (!response.response.success) throw new Error(`Remote export failed: ${response.response.message}`);
7033
- }
7034
- /**
7035
- * Export spans to local files
7036
- */
7037
- exportToLocalFiles(spans) {
7038
- try {
7039
- for (const span of spans) {
7040
- const traceId = span.spanContext().traceId;
7041
- const spanData = this.transformSpanToCleanJSON(span);
7042
- let filePath = this.traceFileMap.get(traceId);
7043
- if (!filePath) {
7044
- const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7045
- filePath = path.join(this.baseDirectory, `${isoTimestamp}_trace_${traceId}.jsonl`);
7046
- this.traceFileMap.set(traceId, filePath);
7047
- }
7048
- const jsonLine = JSON.stringify(spanData) + "\n";
7049
- fs.appendFileSync(filePath, jsonLine, "utf8");
7050
- }
7051
- logger.debug(`Exported ${spans.length} span(s) to trace-specific files in ${this.baseDirectory}`);
7052
- } catch (error) {
7053
- logger.error(`Failed to export spans to local files:`, error);
7054
- throw error;
7055
- }
7056
- }
6986
+ var SpanTransformer = class SpanTransformer {
7057
6987
  /**
7058
6988
  * Transform OpenTelemetry span to clean JSON format with compile-time type safety
7059
6989
  * Return type is derived from protobuf schema but uses clean JSON.
7060
6990
  * We use JSON because serialized protobuf is extremely verbose and not readable.
7061
6991
  */
7062
- transformSpanToCleanJSON(span) {
6992
+ static transformSpanToCleanJSON(span) {
7063
6993
  const isRootSpan = !span.parentSpanId || span.kind === __opentelemetry_api.SpanKind.SERVER;
7064
6994
  const attributes = span.attributes;
7065
- const packageName = this.extractPackageName(attributes);
7066
- const instrumentationName = this.extractInstrumentationName(span, attributes);
7067
- const submoduleName = this.extractSubmoduleName(attributes);
6995
+ const packageName = SpanTransformer.extractPackageName(attributes);
6996
+ const instrumentationName = SpanTransformer.extractInstrumentationName(span, attributes);
6997
+ const submoduleName = SpanTransformer.extractSubmoduleName(attributes);
7068
6998
  const inputValueString = attributes[TdSpanAttributes.INPUT_VALUE];
7069
6999
  const inputData = JSON.parse(inputValueString);
7070
7000
  const inputSchemaMergesString = attributes[TdSpanAttributes.INPUT_SCHEMA_MERGES];
@@ -7130,10 +7060,115 @@ var TdSpanExporter = class {
7130
7060
  };
7131
7061
  }
7132
7062
  /**
7133
- * Transform OpenTelemetry span to protobuf format
7063
+ * Extract package name from attributes or instrumentation library
7134
7064
  */
7135
- transformSpanToProtobuf(span) {
7136
- const cleanSpan = this.transformSpanToCleanJSON(span);
7065
+ static extractPackageName(attributes) {
7066
+ if (attributes[TdSpanAttributes.PACKAGE_NAME]) return attributes[TdSpanAttributes.PACKAGE_NAME];
7067
+ return "unknown";
7068
+ }
7069
+ /**
7070
+ * Extract instrumentation name from span data
7071
+ */
7072
+ static extractInstrumentationName(span, attributes) {
7073
+ if (attributes[TdSpanAttributes.INSTRUMENTATION_NAME]) return attributes[TdSpanAttributes.INSTRUMENTATION_NAME];
7074
+ if (span.instrumentationLibrary?.name) return `tusk-instrumentation-${span.instrumentationLibrary.name}`;
7075
+ return `tusk-instrumentation-${SpanTransformer.extractPackageName(attributes)}`;
7076
+ }
7077
+ /**
7078
+ * Extract submodule name from attributes
7079
+ */
7080
+ static extractSubmoduleName(attributes) {
7081
+ if (attributes[TdSpanAttributes.SUBMODULE_NAME]) return attributes[TdSpanAttributes.SUBMODULE_NAME];
7082
+ }
7083
+ };
7084
+
7085
+ //#endregion
7086
+ //#region src/core/tracing/adapters/FilesystemSpanAdapter.ts
7087
+ /**
7088
+ * Exports spans to local JSONL files organized by trace ID
7089
+ */
7090
+ var FilesystemSpanAdapter = class {
7091
+ constructor(config) {
7092
+ this.name = "filesystem";
7093
+ this.traceFileMap = /* @__PURE__ */ new Map();
7094
+ this.baseDirectory = config.baseDirectory;
7095
+ if (!fs.existsSync(this.baseDirectory)) fs.mkdirSync(this.baseDirectory, { recursive: true });
7096
+ }
7097
+ async exportSpans(spans) {
7098
+ try {
7099
+ for (const span of spans) {
7100
+ const traceId = span.traceId;
7101
+ let filePath = this.traceFileMap.get(traceId);
7102
+ if (!filePath) {
7103
+ const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7104
+ filePath = path.join(this.baseDirectory, `${isoTimestamp}_trace_${traceId}.jsonl`);
7105
+ this.traceFileMap.set(traceId, filePath);
7106
+ }
7107
+ const jsonLine = JSON.stringify(span) + "\n";
7108
+ fs.appendFileSync(filePath, jsonLine, "utf8");
7109
+ }
7110
+ logger.debug(`Exported ${spans.length} span(s) to trace-specific files in ${this.baseDirectory}`);
7111
+ return { code: import_src$3.ExportResultCode.SUCCESS };
7112
+ } catch (error) {
7113
+ logger.error(`Failed to export spans to local files:`, error);
7114
+ return {
7115
+ code: import_src$3.ExportResultCode.FAILED,
7116
+ error: error instanceof Error ? error : /* @__PURE__ */ new Error("Filesystem export failed")
7117
+ };
7118
+ }
7119
+ }
7120
+ async shutdown() {
7121
+ return Promise.resolve();
7122
+ }
7123
+ };
7124
+
7125
+ //#endregion
7126
+ //#region src/core/tracing/adapters/ApiSpanAdapter.ts
7127
+ var import_src$2 = /* @__PURE__ */ __toESM(require_src$6(), 1);
7128
+ const DRIFT_API_PATH = "/api/drift";
7129
+ /**
7130
+ * Exports spans to Tusk backend API via protobuf
7131
+ */
7132
+ var ApiSpanAdapter = class {
7133
+ constructor(config) {
7134
+ this.name = "api";
7135
+ this.observableServiceId = config.observableServiceId;
7136
+ this.environment = config.environment;
7137
+ this.sdkVersion = config.sdkVersion;
7138
+ this.sdkInstanceId = config.sdkInstanceId;
7139
+ const transport = new __protobuf_ts_twirp_transport.TwirpFetchTransport({
7140
+ baseUrl: `${config.tuskBackendBaseUrl}${DRIFT_API_PATH}`,
7141
+ meta: {
7142
+ "x-api-key": config.apiKey,
7143
+ "x-td-skip-instrumentation": "true"
7144
+ }
7145
+ });
7146
+ this.spanExportClient = new __use_tusk_drift_schemas_backend_span_export_service_client.SpanExportServiceClient(transport);
7147
+ logger.debug("ApiSpanAdapter initialized");
7148
+ }
7149
+ async exportSpans(spans) {
7150
+ try {
7151
+ const protoSpans = spans.map((span) => this.transformSpanToProtobuf(span));
7152
+ const request = {
7153
+ observableServiceId: this.observableServiceId,
7154
+ environment: this.environment,
7155
+ sdkVersion: this.sdkVersion,
7156
+ sdkInstanceId: this.sdkInstanceId,
7157
+ spans: protoSpans
7158
+ };
7159
+ const response = await this.spanExportClient.exportSpans(request);
7160
+ if (!response.response.success) throw new Error(`Remote export failed: ${response.response.message}`);
7161
+ logger.debug(`Successfully exported ${spans.length} spans to remote endpoint`);
7162
+ return { code: import_src$2.ExportResultCode.SUCCESS };
7163
+ } catch (error) {
7164
+ logger.error(`Failed to export spans to remote:`, error);
7165
+ return {
7166
+ code: import_src$2.ExportResultCode.FAILED,
7167
+ error: error instanceof Error ? error : /* @__PURE__ */ new Error("API export failed")
7168
+ };
7169
+ }
7170
+ }
7171
+ transformSpanToProtobuf(cleanSpan) {
7137
7172
  return {
7138
7173
  traceId: cleanSpan.traceId,
7139
7174
  spanId: cleanSpan.spanId,
@@ -7151,7 +7186,7 @@ var TdSpanExporter = class {
7151
7186
  outputSchemaHash: cleanSpan.outputSchemaHash || "",
7152
7187
  inputValueHash: cleanSpan.inputValueHash || "",
7153
7188
  outputValueHash: cleanSpan.outputValueHash || "",
7154
- kind: mapOtToPb(span.kind),
7189
+ kind: this.mapSpanKind(cleanSpan.kind),
7155
7190
  status: cleanSpan.status,
7156
7191
  isPreAppStart: cleanSpan.isPreAppStart,
7157
7192
  timestamp: {
@@ -7166,32 +7201,111 @@ var TdSpanExporter = class {
7166
7201
  metadata: toStruct(cleanSpan.metadata)
7167
7202
  };
7168
7203
  }
7204
+ mapSpanKind(kind) {
7205
+ switch (kind) {
7206
+ case __opentelemetry_api.SpanKind.CLIENT: return __use_tusk_drift_schemas_core_span.SpanKind.CLIENT;
7207
+ case __opentelemetry_api.SpanKind.SERVER: return __use_tusk_drift_schemas_core_span.SpanKind.SERVER;
7208
+ case __opentelemetry_api.SpanKind.PRODUCER: return __use_tusk_drift_schemas_core_span.SpanKind.PRODUCER;
7209
+ case __opentelemetry_api.SpanKind.CONSUMER: return __use_tusk_drift_schemas_core_span.SpanKind.CONSUMER;
7210
+ case __opentelemetry_api.SpanKind.INTERNAL: return __use_tusk_drift_schemas_core_span.SpanKind.INTERNAL;
7211
+ default: return __use_tusk_drift_schemas_core_span.SpanKind.UNSPECIFIED;
7212
+ }
7213
+ }
7214
+ async shutdown() {
7215
+ return Promise.resolve();
7216
+ }
7217
+ };
7218
+
7219
+ //#endregion
7220
+ //#region src/core/tracing/TdSpanExporter.ts
7221
+ var import_src$1 = /* @__PURE__ */ __toESM(require_src$6(), 1);
7222
+ var TdSpanExporter = class {
7223
+ constructor(config) {
7224
+ this.adapters = [];
7225
+ this.mode = config.mode;
7226
+ this.setupDefaultAdapters(config);
7227
+ logger.debug(`TdSpanExporter initialized with ${this.adapters.length} adapter(s)`);
7228
+ }
7229
+ setupDefaultAdapters(config) {
7230
+ if (config.useRemoteExport && config.apiKey && config.observableServiceId) {
7231
+ logger.debug("TdSpanExporter using API adapter");
7232
+ this.addAdapter(new ApiSpanAdapter({
7233
+ apiKey: config.apiKey,
7234
+ tuskBackendBaseUrl: config.tuskBackendBaseUrl,
7235
+ observableServiceId: config.observableServiceId,
7236
+ environment: config.environment,
7237
+ sdkVersion: config.sdkVersion,
7238
+ sdkInstanceId: config.sdkInstanceId
7239
+ }));
7240
+ } else {
7241
+ logger.debug("TdSpanExporter falling back to filesystem adapter");
7242
+ this.addAdapter(new FilesystemSpanAdapter({ baseDirectory: config.baseDirectory }));
7243
+ }
7244
+ }
7245
+ getAdapters() {
7246
+ return this.adapters;
7247
+ }
7169
7248
  /**
7170
- * Extract package name from attributes or instrumentation library
7249
+ * Add a custom export adapter
7171
7250
  */
7172
- extractPackageName(attributes) {
7173
- if (attributes[TdSpanAttributes.PACKAGE_NAME]) return attributes[TdSpanAttributes.PACKAGE_NAME];
7174
- return "unknown";
7251
+ addAdapter(adapter) {
7252
+ this.adapters.push(adapter);
7253
+ logger.debug(`Added ${adapter.name} adapter. Total adapters: ${this.adapters.length}`);
7175
7254
  }
7176
7255
  /**
7177
- * Extract instrumentation name from span data
7256
+ * Remove a specific adapter
7178
7257
  */
7179
- extractInstrumentationName(span, attributes) {
7180
- if (attributes[TdSpanAttributes.INSTRUMENTATION_NAME]) return attributes[TdSpanAttributes.INSTRUMENTATION_NAME];
7181
- if (span.instrumentationLibrary?.name) return `tusk-instrumentation-${span.instrumentationLibrary.name}`;
7182
- return `tusk-instrumentation-${this.extractPackageName(attributes)}`;
7258
+ removeAdapter(adapter) {
7259
+ const index = this.adapters.indexOf(adapter);
7260
+ if (index > -1) {
7261
+ this.adapters.splice(index, 1);
7262
+ logger.debug(`Removed ${adapter.name} adapter. Total adapters: ${this.adapters.length}`);
7263
+ }
7183
7264
  }
7184
7265
  /**
7185
- * Extract submodule name from attributes
7266
+ * Clear all adapters
7186
7267
  */
7187
- extractSubmoduleName(attributes) {
7188
- if (attributes[TdSpanAttributes.SUBMODULE_NAME]) return attributes[TdSpanAttributes.SUBMODULE_NAME];
7268
+ clearAdapters() {
7269
+ this.adapters = [];
7270
+ logger.debug("All adapters cleared");
7189
7271
  }
7190
7272
  /**
7191
- * Shutdown the exporter
7273
+ * Set the mode for determining which adapters to run
7274
+ */
7275
+ setMode(mode) {
7276
+ this.mode = mode;
7277
+ }
7278
+ /**
7279
+ * Export spans using all configured adapters
7280
+ */
7281
+ export(spans, resultCallback) {
7282
+ logger.debug(`TdSpanExporter.export() called with ${spans.length} span(s)`);
7283
+ const cleanSpans = spans.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
7284
+ if (this.adapters.length === 0) {
7285
+ logger.debug("No adapters configured");
7286
+ resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
7287
+ return;
7288
+ }
7289
+ const activeAdapters = this.getActiveAdapters();
7290
+ if (activeAdapters.length === 0) {
7291
+ logger.debug(`No active adapters for mode: ${this.mode}`);
7292
+ resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
7293
+ return;
7294
+ }
7295
+ Promise.all(activeAdapters.map((adapter) => adapter.exportSpans(cleanSpans))).then(() => resultCallback({ code: import_src$1.ExportResultCode.SUCCESS })).catch((error) => resultCallback({
7296
+ code: import_src$1.ExportResultCode.FAILED,
7297
+ error
7298
+ }));
7299
+ }
7300
+ getActiveAdapters() {
7301
+ if (this.mode !== TuskDriftMode.RECORD) return this.adapters.filter((adapter) => adapter.name === "in-memory" || adapter.name === "callback");
7302
+ return this.adapters;
7303
+ }
7304
+ /**
7305
+ * Shutdown all adapters
7192
7306
  */
7193
7307
  async shutdown() {
7194
- return Promise.resolve();
7308
+ await Promise.all(this.adapters.map((adapter) => adapter.shutdown()));
7195
7309
  }
7196
7310
  /**
7197
7311
  * Force flush any pending spans
@@ -10493,12 +10607,6 @@ var TuskDriftCore = class TuskDriftCore {
10493
10607
  default: return TuskDriftMode.DISABLED;
10494
10608
  }
10495
10609
  }
10496
- initializeMemoryStore({ baseDirectory }) {
10497
- if (this.isRunningIntegrationTest()) {
10498
- logger.debug(`Initializing memory store with base directory: ${baseDirectory}`);
10499
- this.memoryStore.initialize(baseDirectory);
10500
- } else logger.debug("Not running integration test, skipping memory store initialization");
10501
- }
10502
10610
  registerDefaultInstrumentations() {
10503
10611
  const transforms = this.config.transforms ?? this.initParams.transforms;
10504
10612
  new HttpInstrumentation({
@@ -10546,20 +10654,20 @@ var TuskDriftCore = class TuskDriftCore {
10546
10654
  initializeTracing({ baseDirectory }) {
10547
10655
  const serviceName = this.config.service?.name || "unknown";
10548
10656
  logger.debug(`Initializing OpenTelemetry tracing for service: ${serviceName}`);
10549
- const traceExporter = new TdSpanExporter({
10657
+ this.spanExporter = new TdSpanExporter({
10550
10658
  baseDirectory,
10551
10659
  mode: this.mode,
10552
10660
  useRemoteExport: this.config.recording?.export_spans || false,
10553
10661
  observableServiceId: this.config.service?.id,
10554
10662
  apiKey: this.initParams.apiKey,
10555
10663
  tuskBackendBaseUrl: this.config.tusk_api?.url || "https://api.usetusk.ai",
10556
- environment: this.initParams.env,
10664
+ environment: this.initParams.env || "unknown",
10557
10665
  sdkVersion: SDK_VERSION,
10558
10666
  sdkInstanceId: this.generateSdkInstanceId()
10559
10667
  });
10560
10668
  this.sdk = new __opentelemetry_sdk_node.NodeSDK({
10561
10669
  serviceName,
10562
- spanProcessor: new import_src.BatchSpanProcessor(traceExporter, {
10670
+ spanProcessor: new import_src.BatchSpanProcessor(this.spanExporter, {
10563
10671
  maxQueueSize: 2048,
10564
10672
  maxExportBatchSize: 512,
10565
10673
  scheduledDelayMillis: 2e3,
@@ -10576,6 +10684,11 @@ var TuskDriftCore = class TuskDriftCore {
10576
10684
  initialize(initParams) {
10577
10685
  this.samplingRate = this.config.recording?.sampling_rate ?? 1;
10578
10686
  this.initParams = initParams;
10687
+ if (!this.initParams.env) {
10688
+ const nodeEnv = OriginalGlobalUtils.getOriginalProcessEnvVar("NODE_ENV") || "unknown";
10689
+ logger.warn(`Environment not provided in initialization parameters. Using '${nodeEnv}' as the environment.`);
10690
+ this.initParams.env = nodeEnv;
10691
+ }
10579
10692
  initializeGlobalLogger({
10580
10693
  logLevel: initParams.logLevel || "info",
10581
10694
  prefix: "TuskDrift"
@@ -10588,10 +10701,6 @@ var TuskDriftCore = class TuskDriftCore {
10588
10701
  logger.error("In record mode and export_spans is true, but API key not provided. API key is required to export spans to Tusk backend. Please provide an API key in the initialization parameters.");
10589
10702
  return;
10590
10703
  }
10591
- if (!this.initParams.env) {
10592
- logger.error("Environment not provided. Please provide an environment in the initialization parameters.");
10593
- return;
10594
- }
10595
10704
  if (this.config.recording?.export_spans && !this.config.service?.id) {
10596
10705
  logger.error("Observable service ID not provided. Please provide an observable service ID in the configuration file.");
10597
10706
  return;
@@ -10633,9 +10742,7 @@ var TuskDriftCore = class TuskDriftCore {
10633
10742
  const baseDirectory = this.config.traces?.dir || path.default.join(process.cwd(), ".tusk/traces");
10634
10743
  logger.debug(`Config: ${JSON.stringify(this.config)}`);
10635
10744
  logger.debug(`Base directory: ${baseDirectory}`);
10636
- this.config.transforms ?? this.initParams.transforms;
10637
10745
  this.initializeTracing({ baseDirectory });
10638
- this.initializeMemoryStore({ baseDirectory });
10639
10746
  this.registerDefaultInstrumentations();
10640
10747
  this.initialized = true;
10641
10748
  logger.info("SDK initialized successfully");
@@ -10807,6 +10914,9 @@ var TuskDriftSDK = class {
10807
10914
  markAppAsReady() {
10808
10915
  return this.tuskDrift.markAppAsReady();
10809
10916
  }
10917
+ isAppReady() {
10918
+ return this.tuskDrift.isAppReady();
10919
+ }
10810
10920
  };
10811
10921
  const TuskDrift = new TuskDriftSDK();
10812
10922