@use-tusk/drift-node-sdk 0.1.0 → 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
@@ -34,6 +34,8 @@ let semver = require("semver");
34
34
  semver = __toESM(semver);
35
35
  let require_in_the_middle = require("require-in-the-middle");
36
36
  require_in_the_middle = __toESM(require_in_the_middle);
37
+ let import_in_the_middle = require("import-in-the-middle");
38
+ import_in_the_middle = __toESM(import_in_the_middle);
37
39
  let __use_tusk_drift_schemas_google_protobuf_struct = require("@use-tusk/drift-schemas/google/protobuf/struct");
38
40
  __use_tusk_drift_schemas_google_protobuf_struct = __toESM(__use_tusk_drift_schemas_google_protobuf_struct);
39
41
  let __opentelemetry_api = require("@opentelemetry/api");
@@ -89,7 +91,7 @@ var TdInstrumentationAbstract = class {
89
91
 
90
92
  //#endregion
91
93
  //#region package.json
92
- var version = "0.1.0";
94
+ var version = "0.1.4";
93
95
 
94
96
  //#endregion
95
97
  //#region src/version.ts
@@ -414,10 +416,11 @@ function sendUnpatchedDependencyAlert({ method, spanId, traceId, stackTrace }) {
414
416
 
415
417
  //#endregion
416
418
  //#region src/instrumentation/core/baseClasses/TdInstrumentationBase.ts
417
- var TdInstrumentationBase = class extends TdInstrumentationAbstract {
419
+ var TdInstrumentationBase = class TdInstrumentationBase extends TdInstrumentationAbstract {
418
420
  constructor(instrumentationName, config = {}) {
419
421
  super(instrumentationName, config);
420
422
  this._modules = [];
423
+ this._hooks = [];
421
424
  this._enabled = false;
422
425
  let modules = this.init();
423
426
  if (modules && !Array.isArray(modules)) modules = [modules];
@@ -458,12 +461,35 @@ var TdInstrumentationBase = class extends TdInstrumentationAbstract {
458
461
  const onRequire = (exports$1, name, baseDir) => {
459
462
  return this._onRequire(module$1, exports$1, name, baseDir);
460
463
  };
461
- new require_in_the_middle.Hook([module$1.name], { internals: true }, onRequire);
464
+ const hookFn = (exports$1, name, baseDir) => {
465
+ if (!baseDir && path.default.isAbsolute(name)) {
466
+ const parsedPath = path.default.parse(name);
467
+ name = parsedPath.name;
468
+ baseDir = parsedPath.dir;
469
+ }
470
+ return this._onRequire(module$1, exports$1, name, baseDir || void 0);
471
+ };
472
+ const cjsHook = new require_in_the_middle.Hook([module$1.name], { internals: true }, onRequire);
473
+ this._hooks.push(cjsHook);
474
+ const esmHook = new import_in_the_middle.Hook([module$1.name], { internals: false }, hookFn);
475
+ this._hooks.push(esmHook);
462
476
  }
463
477
  }
464
478
  isEnabled() {
465
479
  return this._enabled;
466
480
  }
481
+ /**
482
+ * Mark a module as patched.
483
+ */
484
+ markModuleAsPatched(moduleExports) {
485
+ TdInstrumentationBase._patchedModules.add(moduleExports);
486
+ }
487
+ /**
488
+ * Check if a module has already been patched.
489
+ */
490
+ isModulePatched(moduleExports) {
491
+ return TdInstrumentationBase._patchedModules.has(moduleExports);
492
+ }
467
493
  _onRequire(module$1, exports$1, name, baseDir) {
468
494
  if (!baseDir) {
469
495
  if (typeof module$1.patch === "function") {
@@ -526,6 +552,7 @@ var TdInstrumentationBase = class extends TdInstrumentationAbstract {
526
552
  }, exports$1);
527
553
  }
528
554
  };
555
+ TdInstrumentationBase._patchedModules = /* @__PURE__ */ new WeakSet();
529
556
 
530
557
  //#endregion
531
558
  //#region src/instrumentation/core/baseClasses/TdInstrumentationNodeModule.ts
@@ -1552,10 +1579,11 @@ async function findMockResponseAsync({ mockRequestData, tuskDrift, inputValueSch
1552
1579
 
1553
1580
  //#endregion
1554
1581
  //#region src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts
1582
+ let ClientRequest;
1555
1583
  /**
1556
1584
  * Mock ClientRequest implementation for Tusk Drift HTTP replay
1557
1585
  */
1558
- var TdMockClientRequest = class extends events.EventEmitter {
1586
+ var TdMockClientRequest = class TdMockClientRequest extends events.EventEmitter {
1559
1587
  constructor(options, spanInfo, callback) {
1560
1588
  super();
1561
1589
  this.INSTRUMENTATION_NAME = "HttpInstrumentation";
@@ -1564,6 +1592,7 @@ var TdMockClientRequest = class extends events.EventEmitter {
1564
1592
  this.playbackStarted = false;
1565
1593
  this.readyToStartPlaybackOnSocketEvent = false;
1566
1594
  this.headers = {};
1595
+ TdMockClientRequest._setupPrototype();
1567
1596
  this.tuskDrift = TuskDriftCore.getInstance();
1568
1597
  this.spanInfo = spanInfo;
1569
1598
  if (!options || Object.keys(options).length === 0) throw new Error("Making a request with empty `options` is not supported in TdMockClientRequest");
@@ -1804,8 +1833,16 @@ var TdMockClientRequest = class extends events.EventEmitter {
1804
1833
  this.socket.destroy();
1805
1834
  return this;
1806
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
+ }
1807
1845
  };
1808
- Object.setPrototypeOf(TdMockClientRequest.prototype, http.ClientRequest.prototype);
1809
1846
 
1810
1847
  //#endregion
1811
1848
  //#region src/instrumentation/libraries/http/HttpReplayHooks.ts
@@ -1930,6 +1967,7 @@ function wrap(target, propertyName, wrapper) {
1930
1967
  wrapped._original = original;
1931
1968
  wrapped._propertyName = propertyName;
1932
1969
  target[propertyName] = wrapped;
1970
+ return wrapped;
1933
1971
  }
1934
1972
 
1935
1973
  //#endregion
@@ -2075,7 +2113,19 @@ var HttpTransformEngine = class {
2075
2113
  const target = span[selector];
2076
2114
  if (!target) return false;
2077
2115
  try {
2078
- 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;
2079
2129
  } catch (error) {
2080
2130
  return false;
2081
2131
  }
@@ -2083,9 +2133,8 @@ var HttpTransformEngine = class {
2083
2133
  }
2084
2134
  compileHeaderAction(headerName, actionFunction, direction) {
2085
2135
  const lowerHeader = headerName.toLowerCase();
2086
- const selector = direction === "inbound" ? "inputValue" : "outputValue";
2087
2136
  return (span) => {
2088
- const target = span[selector];
2137
+ const target = span.inputValue;
2089
2138
  if (!target?.headers) return false;
2090
2139
  let applied = false;
2091
2140
  for (const key of Object.keys(target.headers)) if (key.toLowerCase() === lowerHeader) {
@@ -2106,7 +2155,15 @@ var HttpTransformEngine = class {
2106
2155
  return (span) => {
2107
2156
  const target = span[selector];
2108
2157
  if (!target || target.body === void 0) return false;
2109
- 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));
2110
2167
  return true;
2111
2168
  };
2112
2169
  }
@@ -2212,18 +2269,25 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2212
2269
  _patchHttpModule(httpModule, protocol) {
2213
2270
  const protocolUpper = protocol.toUpperCase();
2214
2271
  logger.debug(`[HttpInstrumentation] Patching ${protocolUpper} module in ${this.mode} mode`);
2215
- if (httpModule._tdPatched) {
2272
+ if (this.isModulePatched(httpModule)) {
2216
2273
  logger.debug(`[HttpInstrumentation] ${protocolUpper} module already patched, skipping`);
2217
2274
  return httpModule;
2218
2275
  }
2219
- this._wrap(httpModule, "request", this._getRequestPatchFn(protocol));
2220
- this._wrap(httpModule, "get", this._getGetPatchFn(protocol));
2276
+ if (httpModule[Symbol.toStringTag] === "Module") {
2277
+ if (httpModule.default) {
2278
+ this._wrap(httpModule.default, "request", this._getRequestPatchFn(protocol));
2279
+ this._wrap(httpModule.default, "get", this._getGetPatchFn(protocol));
2280
+ }
2281
+ } else {
2282
+ this._wrap(httpModule, "request", this._getRequestPatchFn(protocol));
2283
+ this._wrap(httpModule, "get", this._getGetPatchFn(protocol));
2284
+ }
2221
2285
  const HttpServer = httpModule.Server;
2222
2286
  if (HttpServer && HttpServer.prototype) {
2223
2287
  this._wrap(HttpServer.prototype, "emit", this._getServerEmitPatchFn(protocol));
2224
2288
  logger.debug(`[HttpInstrumentation] Wrapped Server.prototype.emit for ${protocolUpper}`);
2225
2289
  }
2226
- httpModule._tdPatched = true;
2290
+ this.markModuleAsPatched(httpModule);
2227
2291
  logger.debug(`[HttpInstrumentation] ${protocolUpper} module patching complete`);
2228
2292
  return httpModule;
2229
2293
  }
@@ -2568,7 +2632,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2568
2632
  }
2569
2633
  });
2570
2634
  }
2571
- _captureClientRequestBody(req, spanInfo, inputValue, schemaMerges) {
2635
+ _captureClientRequestBody(req, spanInfo, inputValue, schemaMerges, onBodyCaptured) {
2572
2636
  const requestBodyChunks = [];
2573
2637
  let requestBodyCaptured = false;
2574
2638
  const originalWrite = req.write?.bind(req);
@@ -2590,6 +2654,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2590
2654
  body: encodedBody,
2591
2655
  bodySize: bodyBuffer.length
2592
2656
  };
2657
+ if (onBodyCaptured) onBodyCaptured(updatedInputValue);
2593
2658
  SpanUtils.addSpanAttributes(spanInfo.span, {
2594
2659
  inputValue: updatedInputValue,
2595
2660
  inputSchemaMerges: {
@@ -2611,7 +2676,10 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2611
2676
  }
2612
2677
  _handleOutboundRequestInSpan(originalRequest, args, spanInfo, inputValue, schemaMerges) {
2613
2678
  const req = originalRequest.apply(this, args);
2614
- this._captureClientRequestBody(req, spanInfo, inputValue, schemaMerges);
2679
+ let completeInputValue = inputValue;
2680
+ this._captureClientRequestBody(req, spanInfo, inputValue, schemaMerges, (updatedInputValue) => {
2681
+ completeInputValue = updatedInputValue;
2682
+ });
2615
2683
  req.on("response", (res) => {
2616
2684
  logger.debug(`[HttpInstrumentation] HTTP response received: ${res.statusCode} (${SpanUtils.getTraceInfo()})`);
2617
2685
  const outputValue = {
@@ -2651,7 +2719,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2651
2719
  },
2652
2720
  headers: { matchImportance: 0 }
2653
2721
  },
2654
- inputValue
2722
+ inputValue: completeInputValue
2655
2723
  });
2656
2724
  } catch (error) {
2657
2725
  logger.error(`[HttpInstrumentation] Error processing response body:`, error);
@@ -2669,7 +2737,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2669
2737
  },
2670
2738
  headers: { matchImportance: 0 }
2671
2739
  },
2672
- inputValue
2740
+ inputValue: completeInputValue
2673
2741
  });
2674
2742
  } catch (error) {
2675
2743
  logger.error(`[HttpInstrumentation] Error adding output attributes to span:`, error);
@@ -2912,7 +2980,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2912
2980
  return fallback;
2913
2981
  }
2914
2982
  _wrap(target, propertyName, wrapper) {
2915
- wrap(target, propertyName, wrapper);
2983
+ return wrap(target, propertyName, wrapper);
2916
2984
  }
2917
2985
  };
2918
2986
 
@@ -3307,7 +3375,7 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3307
3375
  }
3308
3376
  _patchPgModule(pgModule) {
3309
3377
  logger.debug(`[PgInstrumentation] Patching PG module in ${this.mode} mode`);
3310
- if (pgModule._tdPatched) {
3378
+ if (this.isModulePatched(pgModule)) {
3311
3379
  logger.debug(`[PgInstrumentation] PG module already patched, skipping`);
3312
3380
  return pgModule;
3313
3381
  }
@@ -3319,7 +3387,7 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3319
3387
  this._wrap(pgModule.Client.prototype, "connect", this._getConnectPatchFn("client"));
3320
3388
  logger.debug(`[PgInstrumentation] Wrapped Client.prototype.connect`);
3321
3389
  }
3322
- pgModule._tdPatched = true;
3390
+ this.markModuleAsPatched(pgModule);
3323
3391
  logger.debug(`[PgInstrumentation] PG module patching complete`);
3324
3392
  return pgModule;
3325
3393
  }
@@ -3641,7 +3709,7 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3641
3709
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
3642
3710
  }
3643
3711
  _patchPgPoolModule(pgPoolModule) {
3644
- if (pgPoolModule._tdPatched) {
3712
+ if (this.isModulePatched(pgPoolModule)) {
3645
3713
  logger.debug(`[PgInstrumentation] PG Pool module already patched, skipping`);
3646
3714
  return pgPoolModule;
3647
3715
  }
@@ -3653,7 +3721,7 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3653
3721
  this._wrap(pgPoolModule.prototype, "connect", this._getPoolConnectPatchFn());
3654
3722
  logger.debug(`[PgInstrumentation] Wrapped Pool.prototype.connect`);
3655
3723
  }
3656
- pgPoolModule._tdPatched = true;
3724
+ this.markModuleAsPatched(pgPoolModule);
3657
3725
  logger.debug(`[PgInstrumentation] PG Pool module patching complete`);
3658
3726
  return pgPoolModule;
3659
3727
  }
@@ -3764,6 +3832,17 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3764
3832
  }
3765
3833
  };
3766
3834
 
3835
+ //#endregion
3836
+ //#region src/instrumentation/libraries/postgres/types.ts
3837
+ function isPostgresOutputValueType(value) {
3838
+ return value !== null && value !== void 0 && typeof value === "object" && "_tdOriginalFormat" in value && Object.values(PostgresReturnType).includes(value._tdOriginalFormat);
3839
+ }
3840
+ let PostgresReturnType = /* @__PURE__ */ function(PostgresReturnType$1) {
3841
+ PostgresReturnType$1["ARRAY"] = "array";
3842
+ PostgresReturnType$1["OBJECT"] = "object";
3843
+ return PostgresReturnType$1;
3844
+ }({});
3845
+
3767
3846
  //#endregion
3768
3847
  //#region src/instrumentation/libraries/postgres/Instrumentation.ts
3769
3848
  var PostgresInstrumentation = class extends TdInstrumentationBase {
@@ -3782,25 +3861,41 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
3782
3861
  }
3783
3862
  _patchPostgresModule(postgresModule) {
3784
3863
  logger.debug(`[PostgresInstrumentation] Patching Postgres module in ${this.mode} mode`);
3785
- if (postgresModule._tdPatched) {
3864
+ if (this.isModulePatched(postgresModule)) {
3786
3865
  logger.debug(`[PostgresInstrumentation] Postgres module already patched, skipping`);
3787
3866
  return postgresModule;
3788
3867
  }
3789
- if (typeof postgresModule === "function") {
3868
+ const self$1 = this;
3869
+ if (postgresModule[Symbol.toStringTag] === "Module") {
3870
+ logger.debug(`[PostgresInstrumentation] Wrapping ESM default export`);
3871
+ this._wrap(postgresModule, "default", (originalFunction) => {
3872
+ return function(...args) {
3873
+ return self$1._handlePostgresConnection(originalFunction, args);
3874
+ };
3875
+ });
3876
+ } else {
3877
+ logger.debug(`[PostgresInstrumentation] Module is a function (CJS style)`);
3790
3878
  const originalFunction = postgresModule;
3791
- const self$1 = this;
3792
3879
  const wrappedFunction = function(...args) {
3880
+ logger.debug(`[PostgresInstrumentation] Wrapped postgres() (CJS) called with args:`, args);
3793
3881
  return self$1._handlePostgresConnection(originalFunction, args);
3794
3882
  };
3795
3883
  Object.setPrototypeOf(wrappedFunction, Object.getPrototypeOf(originalFunction));
3796
3884
  Object.defineProperty(wrappedFunction, "name", { value: originalFunction.name });
3885
+ for (const key in originalFunction) if (originalFunction.hasOwnProperty(key)) wrappedFunction[key] = originalFunction[key];
3886
+ Object.getOwnPropertyNames(originalFunction).forEach((key) => {
3887
+ if (key !== "prototype" && key !== "length" && key !== "name") {
3888
+ const descriptor = Object.getOwnPropertyDescriptor(originalFunction, key);
3889
+ if (descriptor) Object.defineProperty(wrappedFunction, key, descriptor);
3890
+ }
3891
+ });
3797
3892
  postgresModule = wrappedFunction;
3798
3893
  }
3799
3894
  if (postgresModule.sql && typeof postgresModule.sql === "function") {
3800
3895
  this._wrap(postgresModule, "sql", this._getSqlPatchFn());
3801
3896
  logger.debug(`[PostgresInstrumentation] Wrapped sql function`);
3802
3897
  }
3803
- postgresModule._tdPatched = true;
3898
+ this.markModuleAsPatched(postgresModule);
3804
3899
  logger.debug(`[PostgresInstrumentation] Postgres module patching complete`);
3805
3900
  return postgresModule;
3806
3901
  }
@@ -4262,8 +4357,8 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4262
4357
  const isResultObject = processedResult && typeof processedResult === "object" && "rows" in processedResult;
4263
4358
  const rows = isResultObject ? processedResult.rows || [] : processedResult || [];
4264
4359
  return Object.assign(rows, {
4265
- command: isResultObject ? processedResult.command : "SELECT",
4266
- count: isResultObject ? processedResult.count : rows.length
4360
+ command: isResultObject ? processedResult.command : void 0,
4361
+ count: isResultObject ? processedResult.count : void 0
4267
4362
  });
4268
4363
  }
4269
4364
  async handleReplayUnsafeQuery({ inputValue, spanInfo, submodule, name }) {
@@ -4314,8 +4409,8 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4314
4409
  return Object.values(row);
4315
4410
  });
4316
4411
  return Object.assign(valueArrays, {
4317
- command: isResultObject ? result.command : "SELECT",
4318
- count: isResultObject ? result.count : valueArrays.length
4412
+ command: isResultObject ? result.command : void 0,
4413
+ count: isResultObject ? result.count : void 0
4319
4414
  });
4320
4415
  });
4321
4416
  } });
@@ -4325,61 +4420,40 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4325
4420
  * based on common PostgreSQL data patterns.
4326
4421
  */
4327
4422
  convertPostgresTypes(result) {
4328
- if (!result) return result;
4329
- if (result && typeof result === "object" && "rows" in result) {
4330
- const convertedResult = { ...result };
4331
- if (Array.isArray(result.rows)) convertedResult.rows = result.rows.map((row) => {
4332
- if (typeof row !== "object" || row === null) return row;
4333
- const convertedRow = { ...row };
4334
- Object.keys(row).forEach((fieldName) => {
4335
- const value = row[fieldName];
4336
- if (value === null || value === void 0) return;
4337
- if (typeof value === "string") {
4338
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
4339
- const dateObj = new Date(value);
4340
- if (!isNaN(dateObj.getTime())) convertedRow[fieldName] = dateObj;
4341
- }
4342
- }
4343
- });
4344
- return convertedRow;
4345
- });
4346
- return convertedResult;
4423
+ if (!isPostgresOutputValueType(result)) {
4424
+ logger.error(`[PostgresInstrumentation] output value is not of type PostgresOutputValueType: ${JSON.stringify(result)}`);
4425
+ return;
4426
+ }
4427
+ if (!result) return;
4428
+ const { _tdOriginalFormat: originalFormat,...convertedResult } = result;
4429
+ if (originalFormat === PostgresReturnType.OBJECT) return convertedResult;
4430
+ else if (originalFormat === PostgresReturnType.ARRAY) return convertedResult.rows || [];
4431
+ else {
4432
+ logger.error(`[PostgresInstrumentation] Invalid result format: ${JSON.stringify(result)}`);
4433
+ return;
4347
4434
  }
4348
- if (Array.isArray(result)) return result.map((row) => {
4349
- if (typeof row !== "object" || row === null) return row;
4350
- const convertedRow = { ...row };
4351
- Object.keys(row).forEach((fieldName) => {
4352
- const value = row[fieldName];
4353
- if (value === null || value === void 0) return;
4354
- if (typeof value === "string") {
4355
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
4356
- const dateObj = new Date(value);
4357
- if (!isNaN(dateObj.getTime())) convertedRow[fieldName] = dateObj;
4358
- }
4359
- }
4360
- });
4361
- return convertedRow;
4362
- });
4363
- return result;
4364
4435
  }
4365
4436
  _addOutputAttributesToSpan(spanInfo, result) {
4366
4437
  if (!result) return;
4367
4438
  let outputValue;
4368
- if (Array.isArray(result)) outputValue = {
4369
- count: result.length,
4370
- rows: result,
4371
- command: "SELECT"
4372
- };
4373
- else if (result && typeof result === "object" && "count" in result) outputValue = {
4374
- count: result.count || 0,
4375
- rows: result.rows || result,
4376
- command: result.command || "UNKNOWN"
4377
- };
4378
- else outputValue = {
4379
- count: 1,
4380
- rows: [result],
4381
- command: "UNKNOWN"
4382
- };
4439
+ if (Array.isArray(result)) {
4440
+ logger.debug(`[PostgresInstrumentation] Adding output attributes to span for array result: ${JSON.stringify(result)}`);
4441
+ outputValue = {
4442
+ _tdOriginalFormat: PostgresReturnType.ARRAY,
4443
+ rows: result
4444
+ };
4445
+ } else if (typeof result === "object") {
4446
+ logger.debug(`[PostgresInstrumentation] Adding output attributes to span for object result: ${JSON.stringify(result)}`);
4447
+ outputValue = {
4448
+ _tdOriginalFormat: PostgresReturnType.OBJECT,
4449
+ count: result.count,
4450
+ rows: result.rows,
4451
+ command: result.command
4452
+ };
4453
+ } else {
4454
+ logger.error(`[PostgresInstrumentation] Invalid result format: ${JSON.stringify(result)}`);
4455
+ return;
4456
+ }
4383
4457
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
4384
4458
  }
4385
4459
  _wrap(target, propertyName, wrapper) {
@@ -4413,7 +4487,7 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
4413
4487
  }
4414
4488
  _patchNetModule(netModule) {
4415
4489
  logger.debug(`[TcpInstrumentation] Patching NET module in ${this.mode} mode`);
4416
- if (netModule._tdPatched) {
4490
+ if (this.isModulePatched(netModule)) {
4417
4491
  logger.debug(`[TcpInstrumentation] NET module already patched, skipping`);
4418
4492
  return netModule;
4419
4493
  }
@@ -4430,7 +4504,7 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
4430
4504
  netModule.Socket.prototype.write = function(...args) {
4431
4505
  return self$1._handleTcpCall("write", originalWrite, args, this);
4432
4506
  };
4433
- netModule._tdPatched = true;
4507
+ this.markModuleAsPatched(netModule);
4434
4508
  return netModule;
4435
4509
  }
4436
4510
  _logUnpatchedDependency(methodName, currentSpanInfo, socketContext) {
@@ -4493,7 +4567,7 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
4493
4567
  })];
4494
4568
  }
4495
4569
  _patchJsonwebtokenModule(jwtModule) {
4496
- if (jwtModule._tdPatched) {
4570
+ if (this.isModulePatched(jwtModule)) {
4497
4571
  logger.debug(`[JsonwebtokenInstrumentation] jsonwebtoken module already patched, skipping`);
4498
4572
  return jwtModule;
4499
4573
  }
@@ -4508,7 +4582,7 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
4508
4582
  this._wrap(jwtModule, "sign", this._getSignPatchFn());
4509
4583
  logger.debug(`[JsonwebtokenInstrumentation] Wrapped jwt.sign`);
4510
4584
  }
4511
- jwtModule._tdPatched = true;
4585
+ this.markModuleAsPatched(jwtModule);
4512
4586
  logger.debug(`[JsonwebtokenInstrumentation] jsonwebtoken module patching complete`);
4513
4587
  return jwtModule;
4514
4588
  }
@@ -4906,7 +4980,7 @@ var JwksRsaInstrumentation = class extends TdInstrumentationBase {
4906
4980
  }
4907
4981
  _patchJwksRsaModule(jwksModule) {
4908
4982
  logger.debug(`[JwksRsaInstrumentation] Patching jwks-rsa module, current mode: ${this.tuskDrift.getMode()}`);
4909
- if (jwksModule._tdPatched) {
4983
+ if (this.isModulePatched(jwksModule)) {
4910
4984
  logger.debug(`[JwksRsaInstrumentation] jwks-rsa module already patched, skipping`);
4911
4985
  return jwksModule;
4912
4986
  }
@@ -4915,7 +4989,7 @@ var JwksRsaInstrumentation = class extends TdInstrumentationBase {
4915
4989
  const originalExpressJwtSecret = jwksModule.expressJwtSecret;
4916
4990
  jwksModule.expressJwtSecret = function(options) {
4917
4991
  logger.debug(`[JwksRsaInstrumentation] expressJwtSecret called with options:`, options);
4918
- let modifiedOptions = { ...options };
4992
+ const modifiedOptions = { ...options };
4919
4993
  if (self$1.tuskDrift.getMode() === TuskDriftMode.REPLAY) {
4920
4994
  logger.debug(`[JwksRsaInstrumentation] REPLAY MODE - Disabling rate limiting for expressJwtSecret`);
4921
4995
  modifiedOptions.rateLimit = false;
@@ -4926,7 +5000,7 @@ var JwksRsaInstrumentation = class extends TdInstrumentationBase {
4926
5000
  };
4927
5001
  logger.debug(`[JwksRsaInstrumentation] Patched expressJwtSecret method`);
4928
5002
  }
4929
- jwksModule._tdPatched = true;
5003
+ this.markModuleAsPatched(jwksModule);
4930
5004
  logger.debug(`[JwksRsaInstrumentation] jwks-rsa module patching complete`);
4931
5005
  return jwksModule;
4932
5006
  }
@@ -6060,9 +6134,9 @@ var require_types$3 = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/
6060
6134
  var require_ExportResult = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/ExportResult.js": ((exports) => {
6061
6135
  Object.defineProperty(exports, "__esModule", { value: true });
6062
6136
  exports.ExportResultCode = void 0;
6063
- (function(ExportResultCode$1) {
6064
- ExportResultCode$1[ExportResultCode$1["SUCCESS"] = 0] = "SUCCESS";
6065
- 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";
6066
6140
  })(exports.ExportResultCode || (exports.ExportResultCode = {}));
6067
6141
  }) });
6068
6142
 
@@ -6904,122 +6978,23 @@ var require_src$6 = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/co
6904
6978
  }) });
6905
6979
 
6906
6980
  //#endregion
6907
- //#region src/core/tracing/TdSpanExporter.ts
6908
- var import_src$1 = /* @__PURE__ */ __toESM(require_src$6(), 1);
6909
- const DRIFT_API_PATH = "/api/drift";
6981
+ //#region src/core/tracing/SpanTransformer.ts
6982
+ var import_src$3 = /* @__PURE__ */ __toESM(require_src$6(), 1);
6910
6983
  /**
6911
- * If useRemoteExport is true, TdTraceExporter exports spans to a remote endpoint via protobuf.
6912
- * If useRemoteExport is false, TdTraceExporter stores spans organized by trace ID in separate files.
6913
- * - Each trace gets its own JSONL file: `{baseDirectory}/{timestamp}_trace_{traceId}.jsonl`.
6984
+ * Utility class for transforming OpenTelemetry spans to CleanSpanData
6914
6985
  */
6915
- var TdSpanExporter = class {
6916
- constructor(config) {
6917
- this.traceFileMap = /* @__PURE__ */ new Map();
6918
- this.baseDirectory = config.baseDirectory;
6919
- this.mode = config.mode;
6920
- this.useRemoteExport = config.useRemoteExport;
6921
- this.observableServiceId = config.observableServiceId;
6922
- this.apiKey = config.apiKey;
6923
- this.tuskBackendBaseUrl = config.tuskBackendBaseUrl;
6924
- this.environment = config.environment;
6925
- this.sdkVersion = config.sdkVersion;
6926
- this.sdkInstanceId = config.sdkInstanceId;
6927
- if (!fs.existsSync(this.baseDirectory)) fs.mkdirSync(this.baseDirectory, { recursive: true });
6928
- if (this.useRemoteExport && this.apiKey) {
6929
- const transport = new __protobuf_ts_twirp_transport.TwirpFetchTransport({
6930
- baseUrl: `${this.tuskBackendBaseUrl}${DRIFT_API_PATH}`,
6931
- meta: {
6932
- "x-api-key": this.apiKey,
6933
- "x-td-skip-instrumentation": "true"
6934
- }
6935
- });
6936
- this.spanExportClient = new __use_tusk_drift_schemas_backend_span_export_service_client.SpanExportServiceClient(transport);
6937
- }
6938
- logger.debug(`TdTraceExporter initialized - ${this.useRemoteExport ? "remote export enabled" : "local file export only"}`);
6939
- }
6940
- /**
6941
- * Export spans to trace-specific files and optionally to remote endpoint
6942
- */
6943
- export(spans, resultCallback) {
6944
- logger.debug(`TdTraceExporter.export() called with ${spans.length} span(s)`);
6945
- if (this.mode !== TuskDriftMode.RECORD) {
6946
- logger.debug(`Not recording spans in tuskDriftMode: ${this.mode}`);
6947
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
6948
- return;
6949
- }
6950
- if (this.useRemoteExport) if (this.spanExportClient) this.exportToRemote(spans).then(() => {
6951
- logger.debug(`Successfully exported ${spans.length} spans to remote endpoint`);
6952
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
6953
- }).catch((error) => {
6954
- logger.error(`Failed to export spans to remote:`, error);
6955
- resultCallback({
6956
- code: import_src$1.ExportResultCode.FAILED,
6957
- error: error instanceof Error ? error : /* @__PURE__ */ new Error("Remote export failed")
6958
- });
6959
- });
6960
- else {
6961
- logger.error("Remote export client not initialized, likely because apiKey is not provided");
6962
- resultCallback({
6963
- code: import_src$1.ExportResultCode.FAILED,
6964
- error: /* @__PURE__ */ new Error("Remote export client not initialized, likely because apiKey is not provided")
6965
- });
6966
- }
6967
- else {
6968
- this.exportToLocalFiles(spans);
6969
- resultCallback({ code: import_src$1.ExportResultCode.SUCCESS });
6970
- }
6971
- }
6972
- /**
6973
- * Export spans to remote endpoint via protobuf
6974
- */
6975
- async exportToRemote(spans) {
6976
- if (!this.spanExportClient) throw new Error("Remote export client not initialized");
6977
- if (!this.observableServiceId) throw new Error("Observable service ID not provided in config");
6978
- const protoSpans = spans.map((span) => this.transformSpanToProtobuf(span));
6979
- const request = {
6980
- observableServiceId: this.observableServiceId,
6981
- environment: this.environment,
6982
- sdkVersion: this.sdkVersion,
6983
- sdkInstanceId: this.sdkInstanceId,
6984
- spans: protoSpans
6985
- };
6986
- const response = await this.spanExportClient.exportSpans(request);
6987
- if (!response.response.success) throw new Error(`Remote export failed: ${response.response.message}`);
6988
- }
6989
- /**
6990
- * Export spans to local files
6991
- */
6992
- exportToLocalFiles(spans) {
6993
- try {
6994
- for (const span of spans) {
6995
- const traceId = span.spanContext().traceId;
6996
- const spanData = this.transformSpanToCleanJSON(span);
6997
- let filePath = this.traceFileMap.get(traceId);
6998
- if (!filePath) {
6999
- const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7000
- filePath = path.join(this.baseDirectory, `${isoTimestamp}_trace_${traceId}.jsonl`);
7001
- this.traceFileMap.set(traceId, filePath);
7002
- }
7003
- const jsonLine = JSON.stringify(spanData) + "\n";
7004
- fs.appendFileSync(filePath, jsonLine, "utf8");
7005
- }
7006
- logger.debug(`Exported ${spans.length} span(s) to trace-specific files in ${this.baseDirectory}`);
7007
- } catch (error) {
7008
- logger.error(`Failed to export spans to local files:`, error);
7009
- throw error;
7010
- }
7011
- }
6986
+ var SpanTransformer = class SpanTransformer {
7012
6987
  /**
7013
6988
  * Transform OpenTelemetry span to clean JSON format with compile-time type safety
7014
6989
  * Return type is derived from protobuf schema but uses clean JSON.
7015
6990
  * We use JSON because serialized protobuf is extremely verbose and not readable.
7016
6991
  */
7017
- transformSpanToCleanJSON(span) {
6992
+ static transformSpanToCleanJSON(span) {
7018
6993
  const isRootSpan = !span.parentSpanId || span.kind === __opentelemetry_api.SpanKind.SERVER;
7019
6994
  const attributes = span.attributes;
7020
- const packageName = this.extractPackageName(attributes);
7021
- const instrumentationName = this.extractInstrumentationName(span, attributes);
7022
- const submoduleName = this.extractSubmoduleName(attributes);
6995
+ const packageName = SpanTransformer.extractPackageName(attributes);
6996
+ const instrumentationName = SpanTransformer.extractInstrumentationName(span, attributes);
6997
+ const submoduleName = SpanTransformer.extractSubmoduleName(attributes);
7023
6998
  const inputValueString = attributes[TdSpanAttributes.INPUT_VALUE];
7024
6999
  const inputData = JSON.parse(inputValueString);
7025
7000
  const inputSchemaMergesString = attributes[TdSpanAttributes.INPUT_SCHEMA_MERGES];
@@ -7085,10 +7060,115 @@ var TdSpanExporter = class {
7085
7060
  };
7086
7061
  }
7087
7062
  /**
7088
- * Transform OpenTelemetry span to protobuf format
7063
+ * Extract package name from attributes or instrumentation library
7064
+ */
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
7089
7071
  */
7090
- transformSpanToProtobuf(span) {
7091
- const cleanSpan = this.transformSpanToCleanJSON(span);
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) {
7092
7172
  return {
7093
7173
  traceId: cleanSpan.traceId,
7094
7174
  spanId: cleanSpan.spanId,
@@ -7106,7 +7186,7 @@ var TdSpanExporter = class {
7106
7186
  outputSchemaHash: cleanSpan.outputSchemaHash || "",
7107
7187
  inputValueHash: cleanSpan.inputValueHash || "",
7108
7188
  outputValueHash: cleanSpan.outputValueHash || "",
7109
- kind: mapOtToPb(span.kind),
7189
+ kind: this.mapSpanKind(cleanSpan.kind),
7110
7190
  status: cleanSpan.status,
7111
7191
  isPreAppStart: cleanSpan.isPreAppStart,
7112
7192
  timestamp: {
@@ -7121,32 +7201,111 @@ var TdSpanExporter = class {
7121
7201
  metadata: toStruct(cleanSpan.metadata)
7122
7202
  };
7123
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
+ }
7124
7248
  /**
7125
- * Extract package name from attributes or instrumentation library
7249
+ * Add a custom export adapter
7126
7250
  */
7127
- extractPackageName(attributes) {
7128
- if (attributes[TdSpanAttributes.PACKAGE_NAME]) return attributes[TdSpanAttributes.PACKAGE_NAME];
7129
- return "unknown";
7251
+ addAdapter(adapter) {
7252
+ this.adapters.push(adapter);
7253
+ logger.debug(`Added ${adapter.name} adapter. Total adapters: ${this.adapters.length}`);
7130
7254
  }
7131
7255
  /**
7132
- * Extract instrumentation name from span data
7256
+ * Remove a specific adapter
7133
7257
  */
7134
- extractInstrumentationName(span, attributes) {
7135
- if (attributes[TdSpanAttributes.INSTRUMENTATION_NAME]) return attributes[TdSpanAttributes.INSTRUMENTATION_NAME];
7136
- if (span.instrumentationLibrary?.name) return `tusk-instrumentation-${span.instrumentationLibrary.name}`;
7137
- 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
+ }
7138
7264
  }
7139
7265
  /**
7140
- * Extract submodule name from attributes
7266
+ * Clear all adapters
7141
7267
  */
7142
- extractSubmoduleName(attributes) {
7143
- if (attributes[TdSpanAttributes.SUBMODULE_NAME]) return attributes[TdSpanAttributes.SUBMODULE_NAME];
7268
+ clearAdapters() {
7269
+ this.adapters = [];
7270
+ logger.debug("All adapters cleared");
7271
+ }
7272
+ /**
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;
7144
7303
  }
7145
7304
  /**
7146
- * Shutdown the exporter
7305
+ * Shutdown all adapters
7147
7306
  */
7148
7307
  async shutdown() {
7149
- return Promise.resolve();
7308
+ await Promise.all(this.adapters.map((adapter) => adapter.shutdown()));
7150
7309
  }
7151
7310
  /**
7152
7311
  * Force flush any pending spans
@@ -10448,12 +10607,6 @@ var TuskDriftCore = class TuskDriftCore {
10448
10607
  default: return TuskDriftMode.DISABLED;
10449
10608
  }
10450
10609
  }
10451
- initializeMemoryStore({ baseDirectory }) {
10452
- if (this.isRunningIntegrationTest()) {
10453
- logger.debug(`Initializing memory store with base directory: ${baseDirectory}`);
10454
- this.memoryStore.initialize(baseDirectory);
10455
- } else logger.debug("Not running integration test, skipping memory store initialization");
10456
- }
10457
10610
  registerDefaultInstrumentations() {
10458
10611
  const transforms = this.config.transforms ?? this.initParams.transforms;
10459
10612
  new HttpInstrumentation({
@@ -10501,20 +10654,20 @@ var TuskDriftCore = class TuskDriftCore {
10501
10654
  initializeTracing({ baseDirectory }) {
10502
10655
  const serviceName = this.config.service?.name || "unknown";
10503
10656
  logger.debug(`Initializing OpenTelemetry tracing for service: ${serviceName}`);
10504
- const traceExporter = new TdSpanExporter({
10657
+ this.spanExporter = new TdSpanExporter({
10505
10658
  baseDirectory,
10506
10659
  mode: this.mode,
10507
10660
  useRemoteExport: this.config.recording?.export_spans || false,
10508
10661
  observableServiceId: this.config.service?.id,
10509
10662
  apiKey: this.initParams.apiKey,
10510
10663
  tuskBackendBaseUrl: this.config.tusk_api?.url || "https://api.usetusk.ai",
10511
- environment: this.initParams.env,
10664
+ environment: this.initParams.env || "unknown",
10512
10665
  sdkVersion: SDK_VERSION,
10513
10666
  sdkInstanceId: this.generateSdkInstanceId()
10514
10667
  });
10515
10668
  this.sdk = new __opentelemetry_sdk_node.NodeSDK({
10516
10669
  serviceName,
10517
- spanProcessor: new import_src.BatchSpanProcessor(traceExporter, {
10670
+ spanProcessor: new import_src.BatchSpanProcessor(this.spanExporter, {
10518
10671
  maxQueueSize: 2048,
10519
10672
  maxExportBatchSize: 512,
10520
10673
  scheduledDelayMillis: 2e3,
@@ -10531,6 +10684,11 @@ var TuskDriftCore = class TuskDriftCore {
10531
10684
  initialize(initParams) {
10532
10685
  this.samplingRate = this.config.recording?.sampling_rate ?? 1;
10533
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
+ }
10534
10692
  initializeGlobalLogger({
10535
10693
  logLevel: initParams.logLevel || "info",
10536
10694
  prefix: "TuskDrift"
@@ -10543,10 +10701,6 @@ var TuskDriftCore = class TuskDriftCore {
10543
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.");
10544
10702
  return;
10545
10703
  }
10546
- if (!this.initParams.env) {
10547
- logger.error("Environment not provided. Please provide an environment in the initialization parameters.");
10548
- return;
10549
- }
10550
10704
  if (this.config.recording?.export_spans && !this.config.service?.id) {
10551
10705
  logger.error("Observable service ID not provided. Please provide an observable service ID in the configuration file.");
10552
10706
  return;
@@ -10588,9 +10742,7 @@ var TuskDriftCore = class TuskDriftCore {
10588
10742
  const baseDirectory = this.config.traces?.dir || path.default.join(process.cwd(), ".tusk/traces");
10589
10743
  logger.debug(`Config: ${JSON.stringify(this.config)}`);
10590
10744
  logger.debug(`Base directory: ${baseDirectory}`);
10591
- this.config.transforms ?? this.initParams.transforms;
10592
10745
  this.initializeTracing({ baseDirectory });
10593
- this.initializeMemoryStore({ baseDirectory });
10594
10746
  this.registerDefaultInstrumentations();
10595
10747
  this.initialized = true;
10596
10748
  logger.info("SDK initialized successfully");
@@ -10762,6 +10914,9 @@ var TuskDriftSDK = class {
10762
10914
  markAppAsReady() {
10763
10915
  return this.tuskDrift.markAppAsReady();
10764
10916
  }
10917
+ isAppReady() {
10918
+ return this.tuskDrift.isAppReady();
10919
+ }
10765
10920
  };
10766
10921
  const TuskDrift = new TuskDriftSDK();
10767
10922