@use-tusk/drift-node-sdk 0.1.34 → 0.1.36

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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createRequire } from "node:module";
2
2
  import * as fs$1 from "fs";
3
3
  import fs from "fs";
4
+ import os from "os";
4
5
  import * as path$1 from "path";
5
6
  import path, { normalize } from "path";
6
- import { parse, satisfies } from "semver";
7
- import os from "os";
7
+ import { satisfies } from "semver";
8
8
  import { Hook } from "require-in-the-middle";
9
9
  import { Hook as Hook$1 } from "import-in-the-middle";
10
10
  import yaml from "js-yaml";
@@ -55,207 +55,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
55
55
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
56
56
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
57
57
 
58
- //#endregion
59
- //#region src/nextjs/utils.ts
60
- /**
61
- * Get the installed Next.js version by reading package.json from node_modules.
62
- *
63
- * @returns The Next.js version string, or undefined if not found
64
- */
65
- function getNextjsVersion() {
66
- try {
67
- const nextPackageJsonPath = path$1.join(process.cwd(), "node_modules", "next", "package.json");
68
- if (fs$1.existsSync(nextPackageJsonPath)) return JSON.parse(fs$1.readFileSync(nextPackageJsonPath, "utf-8")).version;
69
- } catch (error) {}
70
- }
71
- /**
72
- * Parse a semantic version string into its components.
73
- *
74
- * @param version - The version string to parse (e.g., "15.0.0-canary.124")
75
- * @returns Parsed version object with major, minor, patch, and prerelease
76
- */
77
- function parseVersion(version$1) {
78
- try {
79
- const parsed = parse(version$1);
80
- if (!parsed) return {
81
- major: void 0,
82
- minor: void 0,
83
- patch: void 0,
84
- prerelease: void 0
85
- };
86
- return {
87
- major: parsed.major,
88
- minor: parsed.minor,
89
- patch: parsed.patch,
90
- prerelease: parsed.prerelease.length > 0 ? parsed.prerelease.join(".") : void 0
91
- };
92
- } catch {
93
- return {
94
- major: void 0,
95
- minor: void 0,
96
- patch: void 0,
97
- prerelease: void 0
98
- };
99
- }
100
- }
101
- /**
102
- * Check if the Next.js version requires the instrumentationHook to be set.
103
- * From Next.js 15.0.0-rc.1 onwards, the instrumentationHook is no longer needed
104
- * and Next.js will warn if it's set.
105
- *
106
- * @param version - The Next.js version string
107
- * @returns true if instrumentationHook should be set, false otherwise
108
- */
109
- function shouldSetInstrumentationHook(version$1) {
110
- if (!version$1) return true;
111
- const { major: major$1, minor, patch, prerelease } = parseVersion(version$1);
112
- if (major$1 === void 0 || minor === void 0 || patch === void 0) return true;
113
- if (major$1 >= 16) return false;
114
- if (major$1 < 15) return true;
115
- if (major$1 === 15) {
116
- if (minor > 0 || patch > 0) return false;
117
- if (minor === 0 && patch === 0 && prerelease === void 0) return false;
118
- if (prerelease?.startsWith("rc.")) {
119
- if (parseInt(prerelease.split(".")[1] || "0", 10) >= 1) return false;
120
- }
121
- if (prerelease?.startsWith("canary.")) {
122
- if (parseInt(prerelease.split(".")[1] || "0", 10) >= 124) return false;
123
- }
124
- return true;
125
- }
126
- return true;
127
- }
128
- /**
129
- * Log a message if debug mode is enabled.
130
- *
131
- * @param debug - Whether debug mode is enabled
132
- * @param message - The message to log
133
- */
134
- function debugLog(debug, message) {
135
- if (debug) console.log(`[Tusk Drift] ${message}`);
136
- }
137
- /**
138
- * Log a warning message if warnings are not suppressed.
139
- *
140
- * @param suppress - Whether to suppress the warning
141
- * @param message - The warning message to log
142
- */
143
- function warn(suppress, message) {
144
- if (!suppress) console.warn(`[Tusk Drift] ${message}`);
145
- }
146
-
147
- //#endregion
148
- //#region src/nextjs/withTuskDrift.ts
149
- /**
150
- * Wraps your Next.js configuration with Tusk Drift instrumentation setup.
151
- *
152
- * This function automatically configures Next.js to work with Tusk Drift by:
153
- * - Enabling the Next.js instrumentation hook (for Next.js < 15.0.0-rc.1)
154
- * - Configuring server external packages to prevent bundling of core instrumentation packages
155
- * - Supporting both webpack and Turbopack bundlers
156
- * - Preserving your existing Next.js configuration and webpack customizations
157
- *
158
- * @param nextConfig - Your existing Next.js configuration object (optional)
159
- * @param options - Additional options to configure Tusk Drift's behavior (optional)
160
- * @returns The wrapped Next.js configuration with Tusk Drift instrumentation enabled
161
- *
162
- * @example
163
- * Basic usage:
164
- * ```javascript
165
- * // next.config.js
166
- * const { withTuskDrift } = require('@use-tusk/drift-node-sdk');
167
- *
168
- * module.exports = withTuskDrift({
169
- * // Your Next.js config
170
- * });
171
- * ```
172
- *
173
- * @example
174
- * With debug logging:
175
- * ```javascript
176
- * // next.config.js
177
- * const { withTuskDrift } = require('@use-tusk/drift-node-sdk');
178
- *
179
- * module.exports = withTuskDrift(
180
- * {
181
- * // Your Next.js config
182
- * },
183
- * {
184
- * debug: true,
185
- * }
186
- * );
187
- * ```
188
- *
189
- * @remarks
190
- * The following packages are added as server externals for both webpack and Turbopack:
191
- * - `require-in-the-middle` - Required for CommonJS module interception
192
- * - `jsonpath` - Required for schema manipulation
193
- * - Additional packages when TUSK_DRIFT_MODE is RECORD or REPLAY (e.g., database clients, etc.)
194
- *
195
- * Works with both webpack (default) and Turbopack (`next dev --turbo`).
196
- */
197
- function withTuskDrift(nextConfig = {}, options = {}) {
198
- const config = nextConfig;
199
- const debug = options.debug || false;
200
- const suppressAllWarnings = options.suppressWarnings || false;
201
- const nextjsVersion = getNextjsVersion();
202
- if (nextjsVersion) debugLog(debug, `Detected Next.js version: ${nextjsVersion}`);
203
- else warn(suppressAllWarnings || false, "Could not detect Next.js version. Some features may not work correctly. If you encounter issues, please ensure Next.js is properly installed.");
204
- const needsInstrumentationHook = !options.disableInstrumentationHook && shouldSetInstrumentationHook(nextjsVersion);
205
- const mode = process.env.TUSK_DRIFT_MODE?.toUpperCase();
206
- const coreExternals = [
207
- "require-in-the-middle",
208
- "jsonpath",
209
- ...mode === "RECORD" || mode === "REPLAY" ? [
210
- "@upstash/redis",
211
- "ioredis",
212
- "pg",
213
- "postgres",
214
- "mysql2",
215
- "@prisma/client",
216
- "@google-cloud/firestore",
217
- "@grpc/grpc-js",
218
- "graphql",
219
- "jsonwebtoken",
220
- "jwks-rsa"
221
- ] : []
222
- ];
223
- const wrappedConfig = {
224
- ...config,
225
- ...needsInstrumentationHook ? { experimental: {
226
- ...config.experimental,
227
- instrumentationHook: true
228
- } } : { experimental: config.experimental },
229
- serverExternalPackages: [...config.serverExternalPackages || [], ...coreExternals],
230
- webpack: (webpackConfig, webpackOptions) => {
231
- if (webpackOptions.isServer) {
232
- const originalExternals = webpackConfig.externals;
233
- const externalsMapping = {};
234
- for (const pkg of coreExternals) {
235
- externalsMapping[pkg] = `commonjs ${pkg}`;
236
- debugLog(debug, `Mapped external ${pkg} -> commonjs ${pkg}`);
237
- }
238
- if (!originalExternals) {
239
- webpackConfig.externals = [externalsMapping];
240
- debugLog(debug, "Created new externals with SDK paths");
241
- } else if (Array.isArray(originalExternals)) {
242
- originalExternals.push(externalsMapping);
243
- debugLog(debug, "Added SDK paths to existing externals array");
244
- } else {
245
- webpackConfig.externals = [originalExternals, externalsMapping];
246
- debugLog(debug, "Wrapped existing externals with SDK paths");
247
- }
248
- }
249
- if (typeof config.webpack === "function") return config.webpack(webpackConfig, webpackOptions);
250
- return webpackConfig;
251
- }
252
- };
253
- if (needsInstrumentationHook) debugLog(debug, "Set experimental.instrumentationHook to true");
254
- else debugLog(debug, "Skipped setting experimental.instrumentationHook (not needed for Next.js 15.0.0-rc.1+)");
255
- if (options.disableInstrumentationHook && nextjsVersion && shouldSetInstrumentationHook(nextjsVersion)) warn(suppressAllWarnings || false, "You disabled instrumentationHook, but your Next.js version requires it. Tusk Drift may not initialize properly. Please remove the disableInstrumentationHook option.");
256
- return wrappedConfig;
257
- }
258
-
259
58
  //#endregion
260
59
  //#region src/instrumentation/core/baseClasses/TdInstrumentationAbstract.ts
261
60
  var TdInstrumentationAbstract = class {
@@ -293,6 +92,7 @@ var TdInstrumentationAbstract = class {
293
92
  function safeJsonStringify(obj) {
294
93
  const seen = /* @__PURE__ */ new WeakSet();
295
94
  return JSON.stringify(obj, (key, value) => {
95
+ if (typeof value === "bigint") return value.toString();
296
96
  if (typeof value === "object" && value !== null) {
297
97
  if (seen.has(value)) return "[Circular]";
298
98
  seen.add(value);
@@ -7915,7 +7715,7 @@ var SpanUtils = class SpanUtils {
7915
7715
  ...addSpanAttributesOptions.submodule && { [TdSpanAttributes.SUBMODULE_NAME]: addSpanAttributesOptions.submodule },
7916
7716
  ...addSpanAttributesOptions.isPreAppStart && { [TdSpanAttributes.IS_PRE_APP_START]: addSpanAttributesOptions.isPreAppStart },
7917
7717
  ...addSpanAttributesOptions.inputValue && { [TdSpanAttributes.INPUT_VALUE]: createSpanInputValue(addSpanAttributesOptions.inputValue) },
7918
- ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: JSON.stringify(addSpanAttributesOptions.outputValue) },
7718
+ ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: safeJsonStringify(addSpanAttributesOptions.outputValue) },
7919
7719
  ...addSpanAttributesOptions.inputSchemaMerges && { [TdSpanAttributes.INPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.inputSchemaMerges) },
7920
7720
  ...addSpanAttributesOptions.outputSchemaMerges && { [TdSpanAttributes.OUTPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.outputSchemaMerges) },
7921
7721
  ...addSpanAttributesOptions.metadata && { [TdSpanAttributes.METADATA]: JSON.stringify(addSpanAttributesOptions.metadata) },
@@ -18762,6 +18562,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18762
18562
  if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({
18763
18563
  noOpRequestHandler: () => {
18764
18564
  process.nextTick(() => {
18565
+ this.status = "connecting";
18566
+ this.emit("connecting");
18567
+ this.status = "connect";
18568
+ this.emit("connect");
18569
+ this.status = "ready";
18765
18570
  this.emit("ready");
18766
18571
  });
18767
18572
  return Promise.resolve();
@@ -18968,6 +18773,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18968
18773
  async _handleReplayConnect(thisContext) {
18969
18774
  logger.debug(`[IORedisInstrumentation] Replaying IORedis connect`);
18970
18775
  process.nextTick(() => {
18776
+ thisContext.status = "connecting";
18777
+ thisContext.emit("connecting");
18778
+ thisContext.status = "connect";
18779
+ thisContext.emit("connect");
18780
+ thisContext.status = "ready";
18971
18781
  thisContext.emit("ready");
18972
18782
  });
18973
18783
  return Promise.resolve();
@@ -22268,11 +22078,34 @@ let PrismaErrorClassName = /* @__PURE__ */ function(PrismaErrorClassName$1) {
22268
22078
  //#endregion
22269
22079
  //#region src/instrumentation/libraries/prisma/Instrumentation.ts
22270
22080
  var import_src$14 = /* @__PURE__ */ __toESM(require_src$7(), 1);
22271
- var PrismaInstrumentation = class extends TdInstrumentationBase {
22081
+ /**
22082
+ * Prisma instrumentation for recording and replaying database operations.
22083
+ *
22084
+ * ## Type deserialization
22085
+ *
22086
+ * Prisma returns special JS types (Date, BigInt, Decimal, Buffer) for certain
22087
+ * column types. JSON round-trip during record/replay loses this type information.
22088
+ * We reconstruct types during replay using two strategies:
22089
+ *
22090
+ * 1. **Model-based operations** (findFirst, create, update, etc.): The middleware
22091
+ * provides the model name, so we look up field types from `_runtimeDataModel`
22092
+ * at replay time. No extra metadata needed during recording.
22093
+ *
22094
+ * 2. **Raw queries** ($queryRaw): No model name is available. During recording,
22095
+ * we sniff JS types from the result and store a per-column `_tdTypeMap`
22096
+ * (e.g., `{bigNum: "bigint", data: "bytes"}`). During replay, we use this
22097
+ * map to reconstruct the values.
22098
+ *
22099
+ * Both strategies share `_reconstructSingleValue` for the actual conversion.
22100
+ * See docs/prisma-type-deserialization-bug.md for the full design doc.
22101
+ */
22102
+ var PrismaInstrumentation = class PrismaInstrumentation extends TdInstrumentationBase {
22272
22103
  constructor(config = {}) {
22273
22104
  super("@prisma/client", config);
22274
22105
  this.INSTRUMENTATION_NAME = "PrismaInstrumentation";
22275
22106
  this.prismaErrorClasses = [];
22107
+ this.prismaClient = null;
22108
+ this.prismaNamespace = null;
22276
22109
  this.mode = config.mode || TuskDriftMode.DISABLED;
22277
22110
  this.tuskDrift = TuskDriftCore.getInstance();
22278
22111
  }
@@ -22289,6 +22122,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22289
22122
  return prismaModule;
22290
22123
  }
22291
22124
  this._storePrismaErrorClasses(prismaModule);
22125
+ this.prismaNamespace = prismaModule.Prisma || prismaModule;
22292
22126
  logger.debug(`[PrismaInstrumentation] Wrapping PrismaClient constructor`);
22293
22127
  this._wrap(prismaModule, "PrismaClient", (OriginalPrismaClient) => {
22294
22128
  const self = this;
@@ -22296,7 +22130,9 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22296
22130
  return class TdPrismaClient {
22297
22131
  constructor(...args) {
22298
22132
  logger.debug(`[PrismaInstrumentation] Creating patched PrismaClient instance`);
22299
- return new OriginalPrismaClient(...args).$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22133
+ const prismaClient = new OriginalPrismaClient(...args);
22134
+ self.prismaClient = prismaClient;
22135
+ const extendedClient = prismaClient.$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22300
22136
  logger.debug(`[PrismaInstrumentation] $allOperations intercepted: ${model}.${operation}`);
22301
22137
  return self._handlePrismaOperation({
22302
22138
  model,
@@ -22305,6 +22141,22 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22305
22141
  query
22306
22142
  });
22307
22143
  } } });
22144
+ if (self.mode === TuskDriftMode.REPLAY) {
22145
+ const originalTransaction = extendedClient.$transaction.bind(extendedClient);
22146
+ extendedClient.$transaction = async function(...txArgs) {
22147
+ const firstArg = txArgs[0];
22148
+ if (typeof firstArg === "function") {
22149
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing interactive $transaction DB connection`);
22150
+ return firstArg(extendedClient);
22151
+ }
22152
+ if (Array.isArray(firstArg)) {
22153
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing sequential $transaction DB connection`);
22154
+ return Promise.all(firstArg);
22155
+ }
22156
+ return originalTransaction(...txArgs);
22157
+ };
22158
+ }
22159
+ return extendedClient;
22308
22160
  }
22309
22161
  };
22310
22162
  });
@@ -22361,7 +22213,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22361
22213
  inputValue,
22362
22214
  isPreAppStart
22363
22215
  }, (spanInfo) => {
22364
- return this._handleRecordPrismaOperation(spanInfo, query, args);
22216
+ return this._handleRecordPrismaOperation(spanInfo, query, args, model);
22365
22217
  });
22366
22218
  },
22367
22219
  spanKind: import_src$14.SpanKind.CLIENT
@@ -22388,13 +22240,15 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22388
22240
  });
22389
22241
  } else return query(args);
22390
22242
  }
22391
- async _handleRecordPrismaOperation(spanInfo, query, args) {
22243
+ async _handleRecordPrismaOperation(spanInfo, query, args, model) {
22392
22244
  try {
22393
22245
  logger.debug(`[PrismaInstrumentation] Recording Prisma operation`);
22394
22246
  const result = await query(args);
22247
+ const typeMap = model ? null : this._buildTypeMap(result);
22395
22248
  const outputValue = {
22396
22249
  prismaResult: result,
22397
- _tdOriginalFormat: "result"
22250
+ _tdOriginalFormat: "result",
22251
+ ...typeMap && { _tdTypeMap: typeMap }
22398
22252
  };
22399
22253
  try {
22400
22254
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
@@ -22457,8 +22311,158 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22457
22311
  });
22458
22312
  throw errorObj;
22459
22313
  }
22314
+ let result = outputValue.prismaResult;
22315
+ try {
22316
+ if (inputValue.model) result = this._reconstructPrismaTypes(result, inputValue.model);
22317
+ else if (outputValue._tdTypeMap) result = this._reconstructFromTypeMap(result, outputValue._tdTypeMap);
22318
+ } catch (reconstructError) {
22319
+ logger.debug(`[PrismaInstrumentation] Failed to reconstruct types: ${reconstructError}`);
22320
+ }
22460
22321
  SpanUtils.endSpan(spanInfo.span, { code: import_src$14.SpanStatusCode.OK });
22461
- return outputValue.prismaResult;
22322
+ return result;
22323
+ }
22324
+ /**
22325
+ * Sniff the JS type of a value returned by Prisma.
22326
+ *
22327
+ * Returns type names that mirror Prisma's internal `QueryIntrospectionBuiltinType`
22328
+ * enum from `@prisma/client/runtime` (used in `deserializeRawResults.ts`):
22329
+ * "bigint" | "bytes" | "decimal" | "datetime"
22330
+ *
22331
+ * We don't import these types directly — we use our own string literals that
22332
+ * match Prisma's naming convention so the type map is self-documenting and
22333
+ * consistent with what Prisma would produce internally.
22334
+ */
22335
+ _sniffType(value) {
22336
+ if (typeof value === "bigint") return "bigint";
22337
+ if (value instanceof Date) return "datetime";
22338
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return "bytes";
22339
+ if (typeof value === "object" && value !== null && typeof value.toFixed === "function" && typeof value.toExponential === "function" && typeof value.toSignificantDigits === "function") return "decimal";
22340
+ return null;
22341
+ }
22342
+ /**
22343
+ * Build a per-column type map by sniffing values in the result.
22344
+ * For arrays of objects (e.g., $queryRaw results), scans all rows to handle
22345
+ * cases where the first row has null values but later rows don't.
22346
+ * Returns null if no special types are detected.
22347
+ */
22348
+ _buildTypeMap(result) {
22349
+ if (result === null || result === void 0) return null;
22350
+ const rows = Array.isArray(result) ? result : typeof result === "object" ? [result] : null;
22351
+ if (!rows || rows.length === 0) return null;
22352
+ const typeMap = {};
22353
+ let hasTypes = false;
22354
+ for (const row of rows) {
22355
+ if (!row || typeof row !== "object") continue;
22356
+ for (const [key, value] of Object.entries(row)) {
22357
+ if (typeMap[key] || value === null || value === void 0) continue;
22358
+ if (Array.isArray(value) && value.length > 0) {
22359
+ const elemType = this._sniffType(value[0]);
22360
+ if (elemType) {
22361
+ typeMap[key] = `${elemType}-array`;
22362
+ hasTypes = true;
22363
+ continue;
22364
+ }
22365
+ }
22366
+ const type = this._sniffType(value);
22367
+ if (type) {
22368
+ typeMap[key] = type;
22369
+ hasTypes = true;
22370
+ }
22371
+ }
22372
+ if (hasTypes && Object.keys(typeMap).length >= Object.keys(row).length) break;
22373
+ }
22374
+ return hasTypes ? typeMap : null;
22375
+ }
22376
+ /**
22377
+ * Reconstruct types from a _tdTypeMap (used for $queryRaw and other model-less operations).
22378
+ */
22379
+ _reconstructFromTypeMap(result, typeMap) {
22380
+ if (result === null || result === void 0) return result;
22381
+ const reconstructRow = (row) => {
22382
+ if (typeof row !== "object" || row === null) return row;
22383
+ for (const [key, type] of Object.entries(typeMap)) {
22384
+ const value = row[key];
22385
+ if (value === null || value === void 0) continue;
22386
+ if (typeof type === "string" && type.endsWith("-array") && Array.isArray(value)) {
22387
+ const baseType = type.replace("-array", "");
22388
+ row[key] = value.map((v) => this._reconstructSingleValue(v, baseType));
22389
+ continue;
22390
+ }
22391
+ row[key] = this._reconstructSingleValue(value, type);
22392
+ }
22393
+ return row;
22394
+ };
22395
+ if (Array.isArray(result)) return result.map(reconstructRow);
22396
+ return reconstructRow(result);
22397
+ }
22398
+ /**
22399
+ * Reconstruct a single value from its JSON-deserialized form back to the
22400
+ * original JS type that Prisma would have returned.
22401
+ *
22402
+ * The `type` parameter uses the same names as Prisma's query engine types
22403
+ * (see _sniffType and PRISMA_SCHEMA_TO_ENGINE_TYPE). Both the model-based
22404
+ * and raw-query replay paths converge here.
22405
+ */
22406
+ _reconstructSingleValue(value, type) {
22407
+ if (value === null || value === void 0) return value;
22408
+ switch (type) {
22409
+ case "bigint":
22410
+ if (typeof value === "string" || typeof value === "number") return BigInt(value);
22411
+ return value;
22412
+ case "datetime":
22413
+ if (typeof value === "string") return new Date(value);
22414
+ return value;
22415
+ case "decimal":
22416
+ if (typeof value === "string" || typeof value === "number") {
22417
+ if (this.prismaNamespace?.Decimal) return new this.prismaNamespace.Decimal(value);
22418
+ const decimalValue = String(value);
22419
+ return {
22420
+ toString: () => decimalValue,
22421
+ toFixed: (dp) => Number(decimalValue).toFixed(dp),
22422
+ valueOf: () => Number(decimalValue),
22423
+ [Symbol.toPrimitive]: (hint) => hint === "string" ? decimalValue : Number(decimalValue)
22424
+ };
22425
+ }
22426
+ return value;
22427
+ case "bytes":
22428
+ if (typeof value === "string") return Buffer.from(value, "base64");
22429
+ if (typeof value === "object" && !Buffer.isBuffer(value)) {
22430
+ const bufferData = value.data || Object.values(value);
22431
+ return Buffer.from(bufferData);
22432
+ }
22433
+ return value;
22434
+ default: return value;
22435
+ }
22436
+ }
22437
+ /**
22438
+ * Reconstruct Prisma types for model-based operations (findFirst, create, update, etc.).
22439
+ *
22440
+ * Uses `prismaClient._runtimeDataModel` — Prisma's internal schema representation
22441
+ * available at runtime — to determine field types. This avoids needing to store
22442
+ * type metadata during recording; the schema itself is the source of truth.
22443
+ *
22444
+ * Handles scalar fields, array fields (e.g., BigInt[]), and nested relations recursively.
22445
+ */
22446
+ _reconstructPrismaTypes(result, modelName) {
22447
+ if (result === null || result === void 0) return result;
22448
+ const runtimeDataModel = this.prismaClient?._runtimeDataModel;
22449
+ if (!runtimeDataModel) return result;
22450
+ if (Array.isArray(result)) return result.map((item) => this._reconstructPrismaTypes(item, modelName));
22451
+ const model = runtimeDataModel.models[modelName];
22452
+ if (!model) return result;
22453
+ if (typeof result !== "object") return result;
22454
+ const fieldTypeMap = new Map(model.fields.map((f) => [f.name, f]));
22455
+ for (const [key, value] of Object.entries(result)) {
22456
+ const field = fieldTypeMap.get(key);
22457
+ if (!field || value === null || value === void 0) continue;
22458
+ if (field.kind === "scalar") {
22459
+ const engineType = PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE[field.type];
22460
+ if (engineType) if (Array.isArray(value)) result[key] = value.map((v) => this._reconstructSingleValue(v, engineType));
22461
+ else result[key] = this._reconstructSingleValue(value, engineType);
22462
+ }
22463
+ if (field.kind === "object" && field.type && typeof value === "object") result[key] = this._reconstructPrismaTypes(value, field.type);
22464
+ }
22465
+ return result;
22462
22466
  }
22463
22467
  _getPrismaErrorClassName(error) {
22464
22468
  for (const errorInfo of this.prismaErrorClasses) if (error instanceof errorInfo.errorClass) return errorInfo.name;
@@ -22483,6 +22487,70 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22483
22487
  wrap(target, propertyName, wrapper);
22484
22488
  }
22485
22489
  };
22490
+ PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE = {
22491
+ DateTime: "datetime",
22492
+ BigInt: "bigint",
22493
+ Decimal: "decimal",
22494
+ Bytes: "bytes"
22495
+ };
22496
+
22497
+ //#endregion
22498
+ //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22499
+ /**
22500
+ * Fake MongoDB Topology for replay mode.
22501
+ *
22502
+ * When BulkOperationBase is constructed, it calls getTopology(collection)
22503
+ * which requires a connected topology. In replay mode, no real connection
22504
+ * exists, so we inject this fake topology to satisfy the constructor's
22505
+ * requirements without hitting a real server.
22506
+ *
22507
+ * The constructor reads:
22508
+ * - topology.lastHello() -> returns {} so all size limits use defaults
22509
+ * - topology.lastIsMaster() -> returns {} (legacy compat)
22510
+ * - topology.s.options -> returns {} so autoEncryption check is false
22511
+ *
22512
+ * The topology is also injected onto the shared MongoClient (via
22513
+ * _injectFakeTopology) and persists across requests. Other driver code
22514
+ * (e.g. ClientSession.loadBalanced getter) accesses
22515
+ * topology.description.type, so we provide a minimal description to
22516
+ * prevent TypeError when the property is read.
22517
+ */
22518
+ var TdFakeTopology = class extends EventEmitter {
22519
+ constructor() {
22520
+ super();
22521
+ this.fakeServer = { description: { address: "fake:27017" } };
22522
+ this.s = { options: {} };
22523
+ this.description = {
22524
+ type: "Unknown",
22525
+ servers: /* @__PURE__ */ new Map(),
22526
+ hasServer: () => false
22527
+ };
22528
+ }
22529
+ lastHello() {
22530
+ return {};
22531
+ }
22532
+ lastIsMaster() {
22533
+ return {};
22534
+ }
22535
+ shouldCheckForSessionSupport() {
22536
+ return false;
22537
+ }
22538
+ hasSessionSupport() {
22539
+ return true;
22540
+ }
22541
+ isConnected() {
22542
+ return true;
22543
+ }
22544
+ isDestroyed() {
22545
+ return false;
22546
+ }
22547
+ selectServerAsync() {
22548
+ return Promise.resolve(this.fakeServer);
22549
+ }
22550
+ selectServer(_selector, _options, callback) {
22551
+ if (typeof callback === "function") callback(null, this.fakeServer);
22552
+ }
22553
+ };
22486
22554
 
22487
22555
  //#endregion
22488
22556
  //#region src/instrumentation/libraries/mongodb/handlers/ConnectionHandler.ts
@@ -22517,7 +22585,7 @@ var ConnectionHandler = class {
22517
22585
  inputValue,
22518
22586
  isPreAppStart: !this.isAppReady()
22519
22587
  }, (spanInfo) => {
22520
- return this.handleReplayConnect(spanInfo, thisArg);
22588
+ return this.handleReplayConnect(spanInfo, thisArg, args);
22521
22589
  });
22522
22590
  }
22523
22591
  });
@@ -22548,17 +22616,21 @@ var ConnectionHandler = class {
22548
22616
  * - DISABLED: Passthrough.
22549
22617
  */
22550
22618
  handleClose(original, thisArg, args) {
22551
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({
22552
- noOpRequestHandler: () => {
22553
- return Promise.resolve();
22554
- },
22555
- isServerRequest: false,
22556
- replayModeHandler: () => {
22557
- logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22558
- return Promise.resolve();
22559
- }
22560
- });
22561
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22619
+ if (this.mode === TuskDriftMode.REPLAY) {
22620
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22621
+ return handleReplayMode({
22622
+ noOpRequestHandler: () => {
22623
+ if (callback) callback();
22624
+ return Promise.resolve();
22625
+ },
22626
+ isServerRequest: false,
22627
+ replayModeHandler: () => {
22628
+ logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22629
+ if (callback) callback();
22630
+ return Promise.resolve();
22631
+ }
22632
+ });
22633
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22562
22634
  originalFunctionCall: () => original.apply(thisArg, args),
22563
22635
  recordModeHandler: ({ isPreAppStart }) => {
22564
22636
  return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
@@ -22588,7 +22660,18 @@ var ConnectionHandler = class {
22588
22660
  return original.apply(thisArg, args);
22589
22661
  }
22590
22662
  handleRecordConnect(spanInfo, original, thisArg, args) {
22591
- return original.apply(thisArg, args).then((result) => {
22663
+ const connectResult = original.apply(thisArg, args);
22664
+ if (!(connectResult && typeof connectResult.then === "function")) {
22665
+ try {
22666
+ logger.debug(`[${this.instrumentationName}] MongoDB connect returned a non-promise value; recording best-effort span`);
22667
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
22668
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22669
+ } catch (error) {
22670
+ logger.error(`[${this.instrumentationName}] Error ending non-promise connect span:`, error);
22671
+ }
22672
+ return Promise.resolve(connectResult);
22673
+ }
22674
+ return connectResult.then((result) => {
22592
22675
  try {
22593
22676
  logger.debug(`[${this.instrumentationName}] MongoDB connection created successfully (${SpanUtils.getTraceInfo()})`);
22594
22677
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
@@ -22611,8 +22694,9 @@ var ConnectionHandler = class {
22611
22694
  throw error;
22612
22695
  });
22613
22696
  }
22614
- handleReplayConnect(spanInfo, thisArg) {
22697
+ handleReplayConnect(spanInfo, thisArg, args) {
22615
22698
  logger.debug(`[${this.instrumentationName}] Replaying MongoDB connection (skipping TCP connect)`);
22699
+ this.injectFakeTopology(thisArg);
22616
22700
  try {
22617
22701
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: {
22618
22702
  connected: true,
@@ -22622,10 +22706,35 @@ var ConnectionHandler = class {
22622
22706
  } catch (error) {
22623
22707
  logger.error(`[${this.instrumentationName}] Error ending replay connect span:`, error);
22624
22708
  }
22709
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22710
+ if (callback) {
22711
+ callback(null, thisArg);
22712
+ return Promise.resolve(thisArg);
22713
+ }
22625
22714
  return Promise.resolve(thisArg);
22626
22715
  }
22716
+ injectFakeTopology(client) {
22717
+ const fakeTopology = new TdFakeTopology();
22718
+ if (!client) return;
22719
+ if (!client.topology) client.topology = fakeTopology;
22720
+ if (client.s && !client.s.topology) client.s.topology = fakeTopology;
22721
+ if (client.s) {
22722
+ if (client.s.hasBeenClosed === true) client.s.hasBeenClosed = false;
22723
+ if (client.s.isMongoClient === false) client.s.isMongoClient = true;
22724
+ }
22725
+ }
22627
22726
  handleRecordClose(spanInfo, original, thisArg, args) {
22628
- return original.apply(thisArg, args).then(() => {
22727
+ const closeResult = original.apply(thisArg, args);
22728
+ if (!(closeResult && typeof closeResult.then === "function")) {
22729
+ try {
22730
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
22731
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22732
+ } catch (error) {
22733
+ logger.error(`[${this.instrumentationName}] Error ending non-promise close span:`, error);
22734
+ }
22735
+ return Promise.resolve();
22736
+ }
22737
+ return closeResult.then(() => {
22629
22738
  try {
22630
22739
  logger.debug(`[${this.instrumentationName}] MongoDB connection closed successfully (${SpanUtils.getTraceInfo()})`);
22631
22740
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
@@ -22706,21 +22815,37 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22706
22815
  });
22707
22816
  if (this._mockLoadPromise) await this._mockLoadPromise;
22708
22817
  }
22709
- async toArray() {
22710
- await this._ensureMockLoaded();
22711
- return [...this.documents];
22818
+ toArray(callback) {
22819
+ const promise = this._ensureMockLoaded().then(() => [...this.documents]);
22820
+ if (typeof callback === "function") {
22821
+ promise.then((docs) => callback(null, docs), (error) => callback(error));
22822
+ return;
22823
+ }
22824
+ return promise;
22712
22825
  }
22713
- async next() {
22714
- await this._ensureMockLoaded();
22715
- return this.documents[this.index++] ?? null;
22826
+ next(callback) {
22827
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
22828
+ if (typeof callback === "function") {
22829
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
22830
+ return;
22831
+ }
22832
+ return promise;
22716
22833
  }
22717
- async tryNext() {
22718
- await this._ensureMockLoaded();
22719
- return this.documents[this.index++] ?? null;
22834
+ tryNext(callback) {
22835
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
22836
+ if (typeof callback === "function") {
22837
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
22838
+ return;
22839
+ }
22840
+ return promise;
22720
22841
  }
22721
- async hasNext() {
22722
- await this._ensureMockLoaded();
22723
- return this.index < this.documents.length;
22842
+ hasNext(callback) {
22843
+ const promise = this._ensureMockLoaded().then(() => this.index < this.documents.length);
22844
+ if (typeof callback === "function") {
22845
+ promise.then((hasNext) => callback(null, hasNext), (error) => callback(error));
22846
+ return;
22847
+ }
22848
+ return promise;
22724
22849
  }
22725
22850
  async forEach(fn) {
22726
22851
  await this._ensureMockLoaded();
@@ -22793,7 +22918,14 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22793
22918
  map() {
22794
22919
  return this;
22795
22920
  }
22796
- async close() {}
22921
+ close(callback) {
22922
+ const promise = Promise.resolve();
22923
+ if (typeof callback === "function") {
22924
+ promise.then(() => callback(), (error) => callback(error));
22925
+ return;
22926
+ }
22927
+ return promise;
22928
+ }
22797
22929
  rewind() {
22798
22930
  this.index = 0;
22799
22931
  }
@@ -22911,45 +23043,6 @@ var TdFakeChangeStream = class {
22911
23043
  }
22912
23044
  };
22913
23045
 
22914
- //#endregion
22915
- //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22916
- /**
22917
- * Fake MongoDB Topology for replay mode.
22918
- *
22919
- * When BulkOperationBase is constructed, it calls getTopology(collection)
22920
- * which requires a connected topology. In replay mode, no real connection
22921
- * exists, so we inject this fake topology to satisfy the constructor's
22922
- * requirements without hitting a real server.
22923
- *
22924
- * The constructor reads:
22925
- * - topology.lastHello() -> returns {} so all size limits use defaults
22926
- * - topology.lastIsMaster() -> returns {} (legacy compat)
22927
- * - topology.s.options -> returns {} so autoEncryption check is false
22928
- *
22929
- * The topology is also injected onto the shared MongoClient (via
22930
- * _injectFakeTopology) and persists across requests. Other driver code
22931
- * (e.g. ClientSession.loadBalanced getter) accesses
22932
- * topology.description.type, so we provide a minimal description to
22933
- * prevent TypeError when the property is read.
22934
- */
22935
- var TdFakeTopology = class extends EventEmitter {
22936
- constructor() {
22937
- super();
22938
- this.s = { options: {} };
22939
- this.description = {
22940
- type: "Unknown",
22941
- servers: /* @__PURE__ */ new Map(),
22942
- hasServer: () => false
22943
- };
22944
- }
22945
- lastHello() {
22946
- return {};
22947
- }
22948
- lastIsMaster() {
22949
- return {};
22950
- }
22951
- };
22952
-
22953
23046
  //#endregion
22954
23047
  //#region node_modules/bson/lib/bson.cjs
22955
23048
  var require_bson = /* @__PURE__ */ __commonJS({ "node_modules/bson/lib/bson.cjs": ((exports) => {
@@ -26999,7 +27092,8 @@ const COLLECTION_METHODS_TO_WRAP = [
26999
27092
  "createIndexes",
27000
27093
  "dropIndex",
27001
27094
  "dropIndexes",
27002
- "indexes"
27095
+ "indexes",
27096
+ "drop"
27003
27097
  ];
27004
27098
  /**
27005
27099
  * Cursor-returning methods on Collection.prototype.
@@ -27024,6 +27118,12 @@ const DB_METHODS_TO_WRAP = [
27024
27118
  * Db.prototype methods that return cursors.
27025
27119
  */
27026
27120
  const DB_CURSOR_METHODS_TO_WRAP = ["listCollections", "aggregate"];
27121
+ const SUPPORTED_MONGODB_VERSIONS = [
27122
+ "4.*",
27123
+ "5.*",
27124
+ "6.*",
27125
+ "7.*"
27126
+ ];
27027
27127
  var MongodbInstrumentation = class extends TdInstrumentationBase {
27028
27128
  constructor(config = {}) {
27029
27129
  super("mongodb", config);
@@ -27035,38 +27135,37 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27035
27135
  init() {
27036
27136
  return [new TdInstrumentationNodeModule({
27037
27137
  name: "mongodb",
27038
- supportedVersions: [
27039
- "5.*",
27040
- "6.*",
27041
- "7.*"
27042
- ],
27043
- patch: (moduleExports) => this._patchMongodbModule(moduleExports),
27138
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27139
+ patch: (moduleExports, moduleVersion) => this._patchMongodbModule(moduleExports, moduleVersion),
27044
27140
  files: [
27045
27141
  new TdInstrumentationNodeModuleFile({
27046
27142
  name: "mongodb/lib/sessions.js",
27047
- supportedVersions: [
27048
- "5.*",
27049
- "6.*",
27050
- "7.*"
27051
- ],
27143
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27144
+ patch: (moduleExports) => this._patchSessionModule(moduleExports)
27145
+ }),
27146
+ new TdInstrumentationNodeModuleFile({
27147
+ name: "mongodb/lib/sessions",
27148
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27052
27149
  patch: (moduleExports) => this._patchSessionModule(moduleExports)
27053
27150
  }),
27054
27151
  new TdInstrumentationNodeModuleFile({
27055
27152
  name: "mongodb/lib/bulk/ordered.js",
27056
- supportedVersions: [
27057
- "5.*",
27058
- "6.*",
27059
- "7.*"
27060
- ],
27153
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27154
+ patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27155
+ }),
27156
+ new TdInstrumentationNodeModuleFile({
27157
+ name: "mongodb/lib/bulk/ordered",
27158
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27061
27159
  patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27062
27160
  }),
27063
27161
  new TdInstrumentationNodeModuleFile({
27064
27162
  name: "mongodb/lib/bulk/unordered.js",
27065
- supportedVersions: [
27066
- "5.*",
27067
- "6.*",
27068
- "7.*"
27069
- ],
27163
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27164
+ patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27165
+ }),
27166
+ new TdInstrumentationNodeModuleFile({
27167
+ name: "mongodb/lib/bulk/unordered",
27168
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27070
27169
  patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27071
27170
  })
27072
27171
  ]
@@ -27077,8 +27176,9 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27077
27176
  * Wraps MongoClient, Collection, Db, and cursor prototypes to intercept
27078
27177
  * all database operations for record/replay.
27079
27178
  */
27080
- _patchMongodbModule(mongodbModule) {
27081
- logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module in ${this.mode} mode`);
27179
+ _patchMongodbModule(mongodbModule, moduleVersion) {
27180
+ this.mongodbMajorVersion = this._getMajorVersion(moduleVersion);
27181
+ logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module v${moduleVersion || "unknown"} in ${this.mode} mode`);
27082
27182
  if (this.isModulePatched(mongodbModule)) {
27083
27183
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module already patched, skipping`);
27084
27184
  return mongodbModule;
@@ -27145,6 +27245,11 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27145
27245
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module patching complete`);
27146
27246
  return mongodbModule;
27147
27247
  }
27248
+ _getMajorVersion(version$1) {
27249
+ if (!version$1) return void 0;
27250
+ const major$1 = Number(version$1.split(".")[0]);
27251
+ return Number.isFinite(major$1) ? major$1 : void 0;
27252
+ }
27148
27253
  /**
27149
27254
  * Patch all Collection.prototype CRUD methods that return Promises.
27150
27255
  * Guards each method with typeof check for version compatibility.
@@ -27170,13 +27275,24 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27170
27275
  const submodule = `collection-${methodName}`;
27171
27276
  return (original) => {
27172
27277
  return function(...args) {
27278
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
27279
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27173
27280
  const collectionName = this?.s?.namespace?.collection;
27174
27281
  const databaseName = this?.s?.namespace?.db;
27175
- const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, args);
27282
+ const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, effectiveArgs);
27176
27283
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27177
- if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, args, inputValue, spanName, submodule);
27178
- if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, args, inputValue, spanName, submodule, methodName);
27179
- return original.apply(this, args);
27284
+ const invokeHandler = () => {
27285
+ if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
27286
+ if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
27287
+ return original.apply(this, effectiveArgs);
27288
+ };
27289
+ if (callback) {
27290
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
27291
+ return;
27292
+ }
27293
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
27294
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
27295
+ return original.apply(this, effectiveArgs);
27180
27296
  };
27181
27297
  };
27182
27298
  }
@@ -27199,7 +27315,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27199
27315
  isPreAppStart,
27200
27316
  stopRecordingChildSpans: true
27201
27317
  }, (spanInfo) => {
27202
- return original.apply(thisArg, args).then((result) => {
27318
+ const resultPromise = original.apply(thisArg, args);
27319
+ return Promise.resolve(resultPromise).then((result) => {
27203
27320
  try {
27204
27321
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27205
27322
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27236,7 +27353,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27236
27353
  },
27237
27354
  isServerRequest: false,
27238
27355
  replayModeHandler: () => {
27239
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
27356
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getNoOpResult(methodName)), {
27240
27357
  name: spanName,
27241
27358
  kind: import_src$12.SpanKind.CLIENT,
27242
27359
  submodule,
@@ -27391,10 +27508,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27391
27508
  };
27392
27509
  if (typeof cursor.toArray === "function") {
27393
27510
  const originalToArray = cursor.toArray.bind(cursor);
27394
- cursor.toArray = () => {
27395
- if (cursorState.recorded) return originalToArray();
27511
+ cursor.toArray = (callback) => {
27512
+ if (cursorState.recorded) {
27513
+ if (typeof callback === "function") {
27514
+ originalToArray().then((result) => callback(null, result)).catch((error) => callback(error));
27515
+ return;
27516
+ }
27517
+ return originalToArray();
27518
+ }
27396
27519
  cursorState.recorded = true;
27397
- return import_src$12.context.with(creationContext, () => {
27520
+ const resultPromise = import_src$12.context.with(creationContext, () => {
27398
27521
  return handleRecordMode({
27399
27522
  originalFunctionCall: () => originalToArray(),
27400
27523
  recordModeHandler: ({ isPreAppStart }) => {
@@ -27434,37 +27557,125 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27434
27557
  spanKind: import_src$12.SpanKind.CLIENT
27435
27558
  });
27436
27559
  });
27560
+ if (typeof callback === "function") {
27561
+ resultPromise.then((result) => callback(null, result)).catch((error) => callback(error));
27562
+ return;
27563
+ }
27564
+ return resultPromise;
27437
27565
  };
27438
27566
  }
27439
27567
  if (typeof cursor.next === "function") {
27440
27568
  const originalNext = cursor.next.bind(cursor);
27441
- cursor.next = async () => {
27442
- if (cursorState.recorded) return originalNext();
27443
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalNext();
27444
- try {
27445
- const doc = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalNext()) : originalNext());
27569
+ cursor.next = (callback) => {
27570
+ if (typeof callback === "function") {
27571
+ if (cursorState.recorded) {
27572
+ let settled$1 = false;
27573
+ const finish$1 = (error, doc) => {
27574
+ if (settled$1) return;
27575
+ settled$1 = true;
27576
+ callback(error, doc);
27577
+ };
27578
+ const maybeResult$1 = originalNext((error, doc) => finish$1(error, doc));
27579
+ Promise.resolve(maybeResult$1).then((doc) => finish$1(null, doc)).catch((error) => finish$1(error, null));
27580
+ return;
27581
+ }
27582
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
27583
+ let settled$1 = false;
27584
+ const finish$1 = (error, doc) => {
27585
+ if (settled$1) return;
27586
+ settled$1 = true;
27587
+ callback(error, doc);
27588
+ };
27589
+ const maybeResult$1 = originalNext((error, doc) => finish$1(error, doc));
27590
+ Promise.resolve(maybeResult$1).then((doc) => finish$1(null, doc)).catch((error) => finish$1(error, null));
27591
+ return;
27592
+ }
27593
+ let settled = false;
27594
+ const finish = (error, doc) => {
27595
+ if (settled) return;
27596
+ settled = true;
27597
+ if (error) {
27598
+ handleCursorError(error);
27599
+ callback(error);
27600
+ return;
27601
+ }
27602
+ if (doc !== null) cursorState.collectedDocuments.push(doc);
27603
+ else finalizeCursorSpan();
27604
+ callback(null, doc);
27605
+ };
27606
+ const runOriginal = () => originalNext((error, doc) => {
27607
+ finish(error, doc);
27608
+ });
27609
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27610
+ Promise.resolve(maybeResult).then((doc) => finish(null, doc)).catch((error) => finish(error, null));
27611
+ return;
27612
+ }
27613
+ if (cursorState.recorded) return callback === void 0 ? originalNext() : originalNext(callback);
27614
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalNext() : originalNext(callback);
27615
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalNext() : originalNext(callback)) : callback === void 0 ? originalNext() : originalNext(callback)).then((doc) => {
27446
27616
  if (doc !== null) cursorState.collectedDocuments.push(doc);
27447
27617
  else finalizeCursorSpan();
27448
27618
  return doc;
27449
- } catch (error) {
27619
+ }).catch((error) => {
27450
27620
  handleCursorError(error);
27451
27621
  throw error;
27452
- }
27622
+ });
27453
27623
  };
27454
27624
  }
27455
27625
  if (typeof cursor.hasNext === "function") {
27456
27626
  const originalHasNext = cursor.hasNext.bind(cursor);
27457
- cursor.hasNext = async () => {
27458
- if (cursorState.recorded) return originalHasNext();
27459
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalHasNext();
27460
- try {
27461
- const result = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalHasNext()) : originalHasNext());
27627
+ cursor.hasNext = (callback) => {
27628
+ if (typeof callback === "function") {
27629
+ if (cursorState.recorded) {
27630
+ let settled$1 = false;
27631
+ const finish$1 = (error, result) => {
27632
+ if (settled$1) return;
27633
+ settled$1 = true;
27634
+ callback(error, result);
27635
+ };
27636
+ const maybeResult$1 = originalHasNext((error, result) => finish$1(error, result));
27637
+ Promise.resolve(maybeResult$1).then((result) => finish$1(null, result)).catch((error) => finish$1(error, false));
27638
+ return;
27639
+ }
27640
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
27641
+ let settled$1 = false;
27642
+ const finish$1 = (error, result) => {
27643
+ if (settled$1) return;
27644
+ settled$1 = true;
27645
+ callback(error, result);
27646
+ };
27647
+ const maybeResult$1 = originalHasNext((error, result) => finish$1(error, result));
27648
+ Promise.resolve(maybeResult$1).then((result) => finish$1(null, result)).catch((error) => finish$1(error, false));
27649
+ return;
27650
+ }
27651
+ let settled = false;
27652
+ const finish = (error, result) => {
27653
+ if (settled) return;
27654
+ settled = true;
27655
+ if (error) {
27656
+ handleCursorError(error);
27657
+ callback(error);
27658
+ return;
27659
+ }
27660
+ if (!result) finalizeCursorSpan();
27661
+ callback(null, result);
27662
+ };
27663
+ const runOriginal = () => originalHasNext((error, result) => {
27664
+ finish(error, result);
27665
+ });
27666
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27667
+ Promise.resolve(maybeResult).then((result) => finish(null, result)).catch((error) => finish(error, false));
27668
+ return;
27669
+ }
27670
+ if (cursorState.recorded) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27671
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27672
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalHasNext() : originalHasNext(callback)) : callback === void 0 ? originalHasNext() : originalHasNext(callback)).then((result) => {
27462
27673
  if (!result) finalizeCursorSpan();
27463
27674
  return result;
27464
- } catch (error) {
27675
+ }).catch((error) => {
27465
27676
  handleCursorError(error);
27466
27677
  throw error;
27467
- }
27678
+ });
27468
27679
  };
27469
27680
  }
27470
27681
  if (typeof cursor.forEach === "function") {
@@ -27520,14 +27731,30 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27520
27731
  }
27521
27732
  if (typeof cursor.close === "function") {
27522
27733
  const originalClose = cursor.close.bind(cursor);
27523
- cursor.close = async () => {
27524
- try {
27525
- await originalClose();
27734
+ cursor.close = (callback) => {
27735
+ if (typeof callback === "function") {
27736
+ let settled = false;
27737
+ const finish = (error) => {
27738
+ if (settled) return;
27739
+ settled = true;
27740
+ if (error) {
27741
+ handleCursorError(error);
27742
+ callback(error);
27743
+ return;
27744
+ }
27745
+ finalizeCursorSpan();
27746
+ callback();
27747
+ };
27748
+ const maybeResult = originalClose((error) => finish(error));
27749
+ Promise.resolve(maybeResult).then(() => finish()).catch((error) => finish(error));
27750
+ return;
27751
+ }
27752
+ return Promise.resolve(callback === void 0 ? originalClose() : originalClose(callback)).then(() => {
27526
27753
  finalizeCursorSpan();
27527
- } catch (error) {
27754
+ }).catch((error) => {
27528
27755
  handleCursorError(error);
27529
27756
  throw error;
27530
- }
27757
+ });
27531
27758
  };
27532
27759
  }
27533
27760
  if (typeof cursor[Symbol.asyncIterator] === "function") {
@@ -27803,6 +28030,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27803
28030
  case "dropIndex": return {};
27804
28031
  case "dropIndexes": return { ok: 1 };
27805
28032
  case "indexes": return [];
28033
+ case "drop": return true;
27806
28034
  default: return null;
27807
28035
  }
27808
28036
  }
@@ -27831,18 +28059,29 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27831
28059
  const submodule = `db-${methodName}`;
27832
28060
  return (original) => {
27833
28061
  return function(...args) {
28062
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
28063
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27834
28064
  const databaseName = this?.databaseName || this?.s?.namespace?.db;
27835
- const inputValue = self._extractDbInput(methodName, databaseName, args);
28065
+ const inputValue = self._extractDbInput(methodName, databaseName, effectiveArgs);
27836
28066
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27837
- if (self.mode === TuskDriftMode.RECORD) {
27838
- if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, args, inputValue, spanName, submodule);
27839
- return self._handleRecordDbMethod(original, this, args, inputValue, spanName, submodule);
27840
- }
27841
- if (self.mode === TuskDriftMode.REPLAY) {
27842
- if (methodName === "createCollection") return self._handleReplayCreateCollection(this, args, inputValue, spanName, submodule);
27843
- return self._handleReplayDbMethod(original, this, args, inputValue, spanName, submodule, methodName);
28067
+ const invokeHandler = () => {
28068
+ if (self.mode === TuskDriftMode.RECORD) {
28069
+ if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, effectiveArgs, inputValue, spanName, submodule);
28070
+ return self._handleRecordDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
28071
+ }
28072
+ if (self.mode === TuskDriftMode.REPLAY) {
28073
+ if (methodName === "createCollection") return self._handleReplayCreateCollection(this, effectiveArgs, inputValue, spanName, submodule);
28074
+ return self._handleReplayDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
28075
+ }
28076
+ return original.apply(this, effectiveArgs);
28077
+ };
28078
+ if (callback) {
28079
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
28080
+ return;
27844
28081
  }
27845
- return original.apply(this, args);
28082
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
28083
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
28084
+ return original.apply(this, effectiveArgs);
27846
28085
  };
27847
28086
  };
27848
28087
  }
@@ -27865,7 +28104,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27865
28104
  isPreAppStart,
27866
28105
  stopRecordingChildSpans: true
27867
28106
  }, (spanInfo) => {
27868
- return original.apply(thisArg, args).then((result) => {
28107
+ const resultPromise = original.apply(thisArg, args);
28108
+ return Promise.resolve(resultPromise).then((result) => {
27869
28109
  try {
27870
28110
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27871
28111
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27902,7 +28142,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27902
28142
  },
27903
28143
  isServerRequest: false,
27904
28144
  replayModeHandler: () => {
27905
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28145
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getDbNoOpResult(methodName)), {
27906
28146
  name: spanName,
27907
28147
  kind: import_src$12.SpanKind.CLIENT,
27908
28148
  submodule,
@@ -27960,7 +28200,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27960
28200
  isPreAppStart,
27961
28201
  stopRecordingChildSpans: true
27962
28202
  }, (spanInfo) => {
27963
- return original.apply(thisArg, args).then((result) => {
28203
+ const resultPromise = original.apply(thisArg, args);
28204
+ return Promise.resolve(resultPromise).then((result) => {
27964
28205
  try {
27965
28206
  const collectionName = args[0];
27966
28207
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: sanitizeBsonValue({ collectionName }) });
@@ -28224,13 +28465,26 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28224
28465
  */
28225
28466
  _getStartSessionWrapper() {
28226
28467
  const self = this;
28468
+ const REPLAY_WITH_TXN_PATCHED = Symbol("tdReplayWithTransactionPatched");
28227
28469
  return (original) => {
28228
28470
  return function(...args) {
28229
- if (self.mode === TuskDriftMode.REPLAY) {
28230
- logger.debug(`[${self.INSTRUMENTATION_NAME}] startSession called in REPLAY mode`);
28231
- return original.apply(this, args);
28471
+ const session = original.apply(this, args);
28472
+ if (self.mode === TuskDriftMode.REPLAY && self.mongodbMajorVersion === 4 && session && typeof session.withTransaction === "function" && !session[REPLAY_WITH_TXN_PATCHED]) {
28473
+ const originalWithTransaction = session.withTransaction.bind(session);
28474
+ session.withTransaction = async (fn, _options) => {
28475
+ try {
28476
+ await Promise.resolve(fn(session));
28477
+ if (typeof session.commitTransaction === "function") return await session.commitTransaction();
28478
+ return;
28479
+ } catch (error) {
28480
+ if (typeof session.abortTransaction === "function") await session.abortTransaction();
28481
+ throw error;
28482
+ }
28483
+ };
28484
+ session.__tdOriginalWithTransaction = originalWithTransaction;
28485
+ session[REPLAY_WITH_TXN_PATCHED] = true;
28232
28486
  }
28233
- return original.apply(this, args);
28487
+ return session;
28234
28488
  };
28235
28489
  };
28236
28490
  }
@@ -28306,7 +28560,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28306
28560
  isPreAppStart,
28307
28561
  stopRecordingChildSpans: true
28308
28562
  }, (spanInfo) => {
28309
- return original.apply(thisArg, args).then((result) => {
28563
+ const resultPromise = original.apply(thisArg, args);
28564
+ return Promise.resolve(resultPromise).then((result) => {
28310
28565
  try {
28311
28566
  addOutputAttributesToSpan(spanInfo, result ?? { success: true });
28312
28567
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28341,7 +28596,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28341
28596
  noOpRequestHandler: () => Promise.resolve(void 0),
28342
28597
  isServerRequest: false,
28343
28598
  replayModeHandler: () => {
28344
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28599
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(void 0), {
28345
28600
  name: spanName,
28346
28601
  kind: import_src$12.SpanKind.CLIENT,
28347
28602
  submodule,
@@ -28615,7 +28870,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28615
28870
  isPreAppStart,
28616
28871
  stopRecordingChildSpans: true
28617
28872
  }, (spanInfo) => {
28618
- return original.apply(thisArg, args).then((result) => {
28873
+ const resultPromise = original.apply(thisArg, args);
28874
+ return Promise.resolve(resultPromise).then((result) => {
28619
28875
  try {
28620
28876
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
28621
28877
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28661,7 +28917,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28661
28917
  },
28662
28918
  isServerRequest: false,
28663
28919
  replayModeHandler: () => {
28664
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28920
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve({
28921
+ acknowledged: false,
28922
+ insertedCount: 0,
28923
+ matchedCount: 0,
28924
+ modifiedCount: 0,
28925
+ deletedCount: 0,
28926
+ upsertedCount: 0,
28927
+ insertedIds: {},
28928
+ upsertedIds: {}
28929
+ }), {
28665
28930
  name: spanName,
28666
28931
  kind: import_src$12.SpanKind.CLIENT,
28667
28932
  submodule,
@@ -40896,7 +41161,7 @@ var require_src = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/sdk-
40896
41161
  //#endregion
40897
41162
  //#region package.json
40898
41163
  var import_src$1 = /* @__PURE__ */ __toESM(require_src(), 1);
40899
- var version = "0.1.34";
41164
+ var version = "0.1.36";
40900
41165
 
40901
41166
  //#endregion
40902
41167
  //#region src/version.ts
@@ -42797,7 +43062,14 @@ var TuskDriftCore = class TuskDriftCore {
42797
43062
  logger.info(`Rust core path enabled at startup (env=${envDisplay}, reason=${status.reason}).`);
42798
43063
  return;
42799
43064
  }
42800
- logger.warn(`Rust core path requested but binding unavailable; falling back to JavaScript path (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43065
+ logger.info(`Rust core path unavailable at startup; using JavaScript path instead (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43066
+ }
43067
+ logStartupSummary() {
43068
+ const serviceName = this.config.service?.name || "unknown";
43069
+ const serviceId = this.config.service?.id || "<unset>";
43070
+ const environment = this.initParams.env || "<unset>";
43071
+ const exportSpans = this.config.recording?.export_spans || false;
43072
+ logger.info(`SDK initialized successfully (version=${SDK_VERSION}, mode=${this.mode}, env=${environment}, service=${serviceName}, serviceId=${serviceId}, exportSpans=${exportSpans}, samplingRate=${this.samplingRate}, logLevel=${logger.getLogLevel()}, runtime=node ${process.version}, platform=${process.platform}/${process.arch}).`);
42801
43073
  }
42802
43074
  validateSamplingRate(value, source) {
42803
43075
  if (typeof value !== "number" || isNaN(value)) {
@@ -43044,7 +43316,7 @@ var TuskDriftCore = class TuskDriftCore {
43044
43316
  this.initializeTracing({ baseDirectory });
43045
43317
  this.createEnvVarsSnapshot();
43046
43318
  this.initialized = true;
43047
- logger.info("SDK initialized successfully");
43319
+ this.logStartupSummary();
43048
43320
  }
43049
43321
  markAppAsReady() {
43050
43322
  if (!this.initialized) {
@@ -43180,5 +43452,5 @@ var TuskDriftSDK = class {
43180
43452
  const TuskDrift = new TuskDriftSDK();
43181
43453
 
43182
43454
  //#endregion
43183
- export { TuskDrift, withTuskDrift };
43455
+ export { TuskDrift };
43184
43456
  //# sourceMappingURL=index.js.map