@use-tusk/drift-node-sdk 0.1.10 → 0.1.12

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/README.md CHANGED
@@ -28,6 +28,7 @@ Tusk Drift currently supports the following packages and versions:
28
28
  - **MySQL**: `mysql2@3.x`
29
29
  - **IORedis**: `ioredis@4.x-5.x`
30
30
  - **GraphQL**: `graphql@15.x-16.x`
31
+ - **Prisma**: `prisma@5.x-6.x`
31
32
  - **JSON Web Tokens**: `jsonwebtoken@5.x-9.x`
32
33
  - **JWKS RSA**: `jwks-rsa@1.x-3.x`
33
34
 
package/dist/index.cjs CHANGED
@@ -230,17 +230,9 @@ function withTuskDrift(nextConfig = {}, options = {}) {
230
230
  const originalExternals = webpackConfig.externals;
231
231
  const coreExternals = ["require-in-the-middle", "jsonpath"];
232
232
  const externalsMapping = {};
233
- try {
234
- const sdkPath = require.resolve("@use-tusk/drift-node-sdk");
235
- const sdkNodeModules = require("path").resolve(sdkPath, "../..", "node_modules");
236
- for (const pkg of coreExternals) {
237
- const pkgPath = require("path").join(sdkNodeModules, pkg);
238
- externalsMapping[pkg] = `commonjs ${pkgPath}`;
239
- debugLog(debug, `Mapped external ${pkg} -> ${pkgPath}`);
240
- }
241
- } catch (e) {
242
- warn(suppressAllWarnings || false, `Could not resolve SDK path, falling back to regular externals: ${e instanceof Error ? e.message : String(e)}`);
243
- for (const pkg of coreExternals) externalsMapping[pkg] = `commonjs ${pkg}`;
233
+ for (const pkg of coreExternals) {
234
+ externalsMapping[pkg] = `commonjs ${pkg}`;
235
+ debugLog(debug, `Mapped external ${pkg} -> commonjs ${pkg}`);
244
236
  }
245
237
  if (!originalExternals) {
246
238
  webpackConfig.externals = [externalsMapping];
@@ -286,7 +278,7 @@ var TdInstrumentationAbstract = class {
286
278
 
287
279
  //#endregion
288
280
  //#region package.json
289
- var version = "0.1.10";
281
+ var version = "0.1.12";
290
282
 
291
283
  //#endregion
292
284
  //#region src/version.ts
@@ -2068,7 +2060,7 @@ var HttpReplayHooks = class {
2068
2060
  auth: requestOptions.auth || void 0,
2069
2061
  agent: requestOptions.agent || void 0,
2070
2062
  protocol,
2071
- hostname: requestOptions.hostname || void 0,
2063
+ hostname: requestOptions.hostname || requestOptions.host || void 0,
2072
2064
  port: requestOptions.port ? Number(requestOptions.port) : void 0,
2073
2065
  method
2074
2066
  };
@@ -3037,13 +3029,29 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
3037
3029
  readable: res.readable
3038
3030
  };
3039
3031
  const responseChunks = [];
3040
- if (res.readable) {
3032
+ let streamConsumptionMode = "NOT_CONSUMING";
3033
+ const originalRead = res.read?.bind(res);
3034
+ if (originalRead) res.read = function read(size) {
3035
+ const chunk = originalRead(size);
3036
+ if (chunk && (streamConsumptionMode === "READ" || streamConsumptionMode === "NOT_CONSUMING")) {
3037
+ streamConsumptionMode = "READ";
3038
+ responseChunks.push(Buffer.from(chunk));
3039
+ }
3040
+ return chunk;
3041
+ };
3042
+ res.once("resume", () => {
3041
3043
  res.on("data", (chunk) => {
3042
- responseChunks.push(chunk);
3044
+ if (chunk && (streamConsumptionMode === "PIPE" || streamConsumptionMode === "NOT_CONSUMING")) {
3045
+ streamConsumptionMode = "PIPE";
3046
+ responseChunks.push(Buffer.from(chunk));
3047
+ }
3043
3048
  });
3044
- res.on("end", async () => {
3045
- if (responseChunks.length > 0) try {
3046
- const responseBuffer = combineChunks(responseChunks);
3049
+ });
3050
+ res.on("end", async (chunk) => {
3051
+ if (chunk && typeof chunk !== "function") responseChunks.push(Buffer.from(chunk));
3052
+ try {
3053
+ if (responseChunks.length > 0) {
3054
+ const responseBuffer = Buffer.concat(responseChunks);
3047
3055
  const rawHeaders = this._captureHeadersFromRawHeaders(res.rawHeaders);
3048
3056
  outputValue.headers = rawHeaders;
3049
3057
  const contentEncoding = rawHeaders["content-encoding"];
@@ -3052,40 +3060,24 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
3052
3060
  contentEncoding
3053
3061
  });
3054
3062
  outputValue.bodySize = responseBuffer.length;
3055
- this._addOutputAttributesToSpan({
3056
- spanInfo,
3057
- outputValue,
3058
- statusCode: res.statusCode || 1,
3059
- outputSchemaMerges: {
3060
- body: {
3061
- encoding: __use_tusk_drift_schemas_core_json_schema.EncodingType.BASE64,
3062
- decodedType: getDecodedType(outputValue.headers["content-type"] || "")
3063
- },
3064
- headers: { matchImportance: 0 }
3065
- },
3066
- inputValue: completeInputValue
3067
- });
3068
- } catch (error) {
3069
- logger.error(`[HttpInstrumentation] Error processing response body:`, error);
3070
3063
  }
3071
- });
3072
- } else try {
3073
- this._addOutputAttributesToSpan({
3074
- spanInfo,
3075
- outputValue,
3076
- statusCode: res.statusCode || 1,
3077
- outputSchemaMerges: {
3078
- body: {
3079
- encoding: __use_tusk_drift_schemas_core_json_schema.EncodingType.BASE64,
3080
- decodedType: getDecodedType(outputValue.headers["content-type"] || "")
3064
+ this._addOutputAttributesToSpan({
3065
+ spanInfo,
3066
+ outputValue,
3067
+ statusCode: res.statusCode || 1,
3068
+ outputSchemaMerges: {
3069
+ body: {
3070
+ encoding: __use_tusk_drift_schemas_core_json_schema.EncodingType.BASE64,
3071
+ decodedType: getDecodedType(outputValue.headers["content-type"] || "")
3072
+ },
3073
+ headers: { matchImportance: 0 }
3081
3074
  },
3082
- headers: { matchImportance: 0 }
3083
- },
3084
- inputValue: completeInputValue
3085
- });
3086
- } catch (error) {
3087
- logger.error(`[HttpInstrumentation] Error adding output attributes to span:`, error);
3088
- }
3075
+ inputValue: completeInputValue
3076
+ });
3077
+ } catch (error) {
3078
+ logger.error(`[HttpInstrumentation] Error processing response body:`, error);
3079
+ }
3080
+ });
3089
3081
  });
3090
3082
  req.on("error", (error) => {
3091
3083
  try {
@@ -9948,6 +9940,239 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
9948
9940
  }
9949
9941
  };
9950
9942
 
9943
+ //#endregion
9944
+ //#region src/instrumentation/libraries/prisma/types.ts
9945
+ /**
9946
+ * Prisma error class names for proper error handling
9947
+ */
9948
+ let PrismaErrorClassName = /* @__PURE__ */ function(PrismaErrorClassName$1) {
9949
+ PrismaErrorClassName$1["PrismaClientKnownRequestError"] = "PrismaClientKnownRequestError";
9950
+ PrismaErrorClassName$1["PrismaClientUnknownRequestError"] = "PrismaClientUnknownRequestError";
9951
+ PrismaErrorClassName$1["PrismaClientInitializationError"] = "PrismaClientInitializationError";
9952
+ PrismaErrorClassName$1["PrismaClientValidationError"] = "PrismaClientValidationError";
9953
+ PrismaErrorClassName$1["PrismaClientRustPanicError"] = "PrismaClientRustPanicError";
9954
+ PrismaErrorClassName$1["NotFoundError"] = "NotFoundError";
9955
+ return PrismaErrorClassName$1;
9956
+ }({});
9957
+
9958
+ //#endregion
9959
+ //#region src/instrumentation/libraries/prisma/Instrumentation.ts
9960
+ var PrismaInstrumentation = class extends TdInstrumentationBase {
9961
+ constructor(config = {}) {
9962
+ super("@prisma/client", config);
9963
+ this.INSTRUMENTATION_NAME = "PrismaInstrumentation";
9964
+ this.prismaErrorClasses = [];
9965
+ this.mode = config.mode || TuskDriftMode.DISABLED;
9966
+ this.tuskDrift = TuskDriftCore.getInstance();
9967
+ }
9968
+ init() {
9969
+ return [new TdInstrumentationNodeModule({
9970
+ name: "@prisma/client",
9971
+ supportedVersions: ["5.*", "6.*"],
9972
+ patch: (moduleExports) => this._patchPrismaModule(moduleExports)
9973
+ })];
9974
+ }
9975
+ _patchPrismaModule(prismaModule) {
9976
+ if (this.isModulePatched(prismaModule)) {
9977
+ logger.debug(`[PrismaInstrumentation] Prisma module already patched, skipping`);
9978
+ return prismaModule;
9979
+ }
9980
+ this._storePrismaErrorClasses(prismaModule);
9981
+ logger.debug(`[PrismaInstrumentation] Wrapping PrismaClient constructor`);
9982
+ this._wrap(prismaModule, "PrismaClient", (OriginalPrismaClient) => {
9983
+ const self = this;
9984
+ logger.debug(`[PrismaInstrumentation] PrismaClient wrapper called`);
9985
+ return class TdPrismaClient {
9986
+ constructor(...args) {
9987
+ logger.debug(`[PrismaInstrumentation] Creating patched PrismaClient instance`);
9988
+ return new OriginalPrismaClient(...args).$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
9989
+ logger.debug(`[PrismaInstrumentation] $allOperations intercepted: ${model}.${operation}`);
9990
+ return self._handlePrismaOperation({
9991
+ model,
9992
+ operation,
9993
+ args: operationArgs,
9994
+ query
9995
+ });
9996
+ } } });
9997
+ }
9998
+ };
9999
+ });
10000
+ this.markModuleAsPatched(prismaModule);
10001
+ logger.debug(`[PrismaInstrumentation] Prisma module patching complete`);
10002
+ return prismaModule;
10003
+ }
10004
+ _storePrismaErrorClasses(moduleExports) {
10005
+ const prismaNamespace = moduleExports.Prisma || {};
10006
+ this.prismaErrorClasses = [
10007
+ {
10008
+ name: PrismaErrorClassName.PrismaClientKnownRequestError,
10009
+ errorClass: moduleExports.PrismaClientKnownRequestError || prismaNamespace.PrismaClientKnownRequestError
10010
+ },
10011
+ {
10012
+ name: PrismaErrorClassName.PrismaClientUnknownRequestError,
10013
+ errorClass: moduleExports.PrismaClientUnknownRequestError || prismaNamespace.PrismaClientUnknownRequestError
10014
+ },
10015
+ {
10016
+ name: PrismaErrorClassName.PrismaClientInitializationError,
10017
+ errorClass: moduleExports.PrismaClientInitializationError || prismaNamespace.PrismaClientInitializationError
10018
+ },
10019
+ {
10020
+ name: PrismaErrorClassName.PrismaClientValidationError,
10021
+ errorClass: moduleExports.PrismaClientValidationError || prismaNamespace.PrismaClientValidationError
10022
+ },
10023
+ {
10024
+ name: PrismaErrorClassName.PrismaClientRustPanicError,
10025
+ errorClass: moduleExports.PrismaClientRustPanicError || prismaNamespace.PrismaClientRustPanicError
10026
+ },
10027
+ {
10028
+ name: PrismaErrorClassName.NotFoundError,
10029
+ errorClass: moduleExports.NotFoundError || prismaNamespace.NotFoundError
10030
+ }
10031
+ ];
10032
+ }
10033
+ _handlePrismaOperation({ model, operation, args, query }) {
10034
+ const inputValue = {
10035
+ model,
10036
+ operation,
10037
+ args
10038
+ };
10039
+ logger.debug(`[PrismaInstrumentation] Intercepted Prisma operation: ${model}.${operation} in ${this.mode} mode`);
10040
+ if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
10041
+ originalFunctionCall: () => query(args),
10042
+ recordModeHandler: ({ isPreAppStart }) => {
10043
+ return SpanUtils.createAndExecuteSpan(this.mode, () => query(args), {
10044
+ name: `prisma.${operation}`,
10045
+ kind: __opentelemetry_api.SpanKind.CLIENT,
10046
+ submodule: model,
10047
+ packageType: __use_tusk_drift_schemas_core_span.PackageType.UNSPECIFIED,
10048
+ packageName: "@prisma/client",
10049
+ instrumentationName: this.INSTRUMENTATION_NAME,
10050
+ inputValue,
10051
+ isPreAppStart
10052
+ }, (spanInfo) => {
10053
+ return this._handleRecordPrismaOperation(spanInfo, query, args);
10054
+ });
10055
+ },
10056
+ spanKind: __opentelemetry_api.SpanKind.CLIENT
10057
+ });
10058
+ else if (this.mode === TuskDriftMode.REPLAY) {
10059
+ const stackTrace = captureStackTrace(["PrismaInstrumentation"]);
10060
+ return handleReplayMode({
10061
+ noOpRequestHandler: () => query(args),
10062
+ isServerRequest: false,
10063
+ replayModeHandler: () => {
10064
+ return SpanUtils.createAndExecuteSpan(this.mode, () => query(args), {
10065
+ name: `prisma.${operation}`,
10066
+ kind: __opentelemetry_api.SpanKind.CLIENT,
10067
+ submodule: model,
10068
+ packageType: __use_tusk_drift_schemas_core_span.PackageType.UNSPECIFIED,
10069
+ packageName: "@prisma/client",
10070
+ instrumentationName: this.INSTRUMENTATION_NAME,
10071
+ inputValue,
10072
+ isPreAppStart: false
10073
+ }, (spanInfo) => {
10074
+ return this._handleReplayPrismaOperation(spanInfo, inputValue, stackTrace);
10075
+ });
10076
+ }
10077
+ });
10078
+ } else return query(args);
10079
+ }
10080
+ async _handleRecordPrismaOperation(spanInfo, query, args) {
10081
+ try {
10082
+ logger.debug(`[PrismaInstrumentation] Recording Prisma operation`);
10083
+ const result = await query(args);
10084
+ const outputValue = {
10085
+ prismaResult: result,
10086
+ _tdOriginalFormat: "result"
10087
+ };
10088
+ try {
10089
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
10090
+ SpanUtils.endSpan(spanInfo.span, { code: __opentelemetry_api.SpanStatusCode.OK });
10091
+ } catch (spanError) {
10092
+ logger.error(`[PrismaInstrumentation] error adding span attributes:`, spanError);
10093
+ }
10094
+ return result;
10095
+ } catch (error) {
10096
+ logger.debug(`[PrismaInstrumentation] Prisma operation error: ${error.message}`);
10097
+ try {
10098
+ const errorClassName = this._getPrismaErrorClassName(error);
10099
+ const errorWithClassName = this._cloneError(error);
10100
+ if (errorClassName) errorWithClassName.customTdName = errorClassName;
10101
+ const outputValue = {
10102
+ prismaResult: errorWithClassName,
10103
+ _tdOriginalFormat: "error"
10104
+ };
10105
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
10106
+ SpanUtils.endSpan(spanInfo.span, {
10107
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
10108
+ message: error.message
10109
+ });
10110
+ } catch (spanError) {
10111
+ logger.error(`[PrismaInstrumentation] error extracting error and adding span attributes:`, spanError);
10112
+ }
10113
+ throw error;
10114
+ }
10115
+ }
10116
+ async _handleReplayPrismaOperation(spanInfo, inputValue, stackTrace) {
10117
+ const mockData = await findMockResponseAsync({
10118
+ mockRequestData: {
10119
+ traceId: spanInfo.traceId,
10120
+ spanId: spanInfo.spanId,
10121
+ name: `prisma.${inputValue.operation}`,
10122
+ inputValue,
10123
+ packageName: "@prisma/client",
10124
+ instrumentationName: this.INSTRUMENTATION_NAME,
10125
+ submoduleName: inputValue.model,
10126
+ kind: __opentelemetry_api.SpanKind.CLIENT,
10127
+ stackTrace
10128
+ },
10129
+ tuskDrift: this.tuskDrift
10130
+ });
10131
+ if (!mockData) {
10132
+ logger.warn(`[PrismaInstrumentation] No mock data found for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
10133
+ throw new Error(`[PrismaInstrumentation] No matching mock found for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
10134
+ }
10135
+ logger.debug(`[PrismaInstrumentation] Found mock data for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
10136
+ const outputValue = mockData.result;
10137
+ if (outputValue._tdOriginalFormat === "error") {
10138
+ const errorObj = outputValue.prismaResult;
10139
+ if (errorObj.customTdName) {
10140
+ const errorClass = this._getPrismaErrorClassFromName(errorObj.customTdName);
10141
+ if (errorClass) Object.setPrototypeOf(errorObj, errorClass.prototype);
10142
+ }
10143
+ SpanUtils.endSpan(spanInfo.span, {
10144
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
10145
+ message: errorObj.message || "Prisma error"
10146
+ });
10147
+ throw errorObj;
10148
+ }
10149
+ SpanUtils.endSpan(spanInfo.span, { code: __opentelemetry_api.SpanStatusCode.OK });
10150
+ return outputValue.prismaResult;
10151
+ }
10152
+ _getPrismaErrorClassName(error) {
10153
+ for (const errorInfo of this.prismaErrorClasses) if (error instanceof errorInfo.errorClass) return errorInfo.name;
10154
+ }
10155
+ _getPrismaErrorClassFromName(className) {
10156
+ for (const errorInfo of this.prismaErrorClasses) if (errorInfo.name === className) return errorInfo.errorClass;
10157
+ return null;
10158
+ }
10159
+ /**
10160
+ * Deep clone an error object to make it serializable
10161
+ */
10162
+ _cloneError(error) {
10163
+ const cloned = new Error(error.message);
10164
+ cloned.name = error.name;
10165
+ cloned.stack = error.stack;
10166
+ for (const key in error) if (error.hasOwnProperty(key)) try {
10167
+ cloned[key] = error[key];
10168
+ } catch (e) {}
10169
+ return cloned;
10170
+ }
10171
+ _wrap(target, propertyName, wrapper) {
10172
+ wrap(target, propertyName, wrapper);
10173
+ }
10174
+ };
10175
+
9951
10176
  //#endregion
9952
10177
  //#region node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js
9953
10178
  var require_suppress_tracing = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js": ((exports) => {
@@ -12978,6 +13203,41 @@ var TuskDriftCore = class TuskDriftCore {
12978
13203
  default: return TuskDriftMode.DISABLED;
12979
13204
  }
12980
13205
  }
13206
+ validateSamplingRate(value, source) {
13207
+ if (typeof value !== "number" || isNaN(value)) {
13208
+ logger.warn(`Invalid sampling rate from ${source}: not a number. Ignoring.`);
13209
+ return false;
13210
+ }
13211
+ if (value < 0 || value > 1) {
13212
+ logger.warn(`Invalid sampling rate from ${source}: ${value}. Must be between 0.0 and 1.0. Ignoring.`);
13213
+ return false;
13214
+ }
13215
+ return true;
13216
+ }
13217
+ determineSamplingRate(initParams) {
13218
+ if (initParams.samplingRate !== void 0) {
13219
+ if (this.validateSamplingRate(initParams.samplingRate, "init params")) {
13220
+ logger.debug(`Using sampling rate from init params: ${initParams.samplingRate}`);
13221
+ return initParams.samplingRate;
13222
+ }
13223
+ }
13224
+ const envSamplingRate = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_SAMPLING_RATE");
13225
+ if (envSamplingRate !== void 0) {
13226
+ const parsed = parseFloat(envSamplingRate);
13227
+ if (this.validateSamplingRate(parsed, "TUSK_SAMPLING_RATE env var")) {
13228
+ logger.debug(`Using sampling rate from TUSK_SAMPLING_RATE env var: ${parsed}`);
13229
+ return parsed;
13230
+ }
13231
+ }
13232
+ if (this.config.recording?.sampling_rate !== void 0) {
13233
+ if (this.validateSamplingRate(this.config.recording.sampling_rate, "config.yaml")) {
13234
+ logger.debug(`Using sampling rate from config.yaml: ${this.config.recording.sampling_rate}`);
13235
+ return this.config.recording.sampling_rate;
13236
+ }
13237
+ }
13238
+ logger.debug("Using default sampling rate: 1.0");
13239
+ return 1;
13240
+ }
12981
13241
  registerDefaultInstrumentations() {
12982
13242
  const transforms = this.config.transforms ?? this.initParams.transforms;
12983
13243
  new HttpInstrumentation({
@@ -13042,6 +13302,10 @@ var TuskDriftCore = class TuskDriftCore {
13042
13302
  enabled: true,
13043
13303
  mode: this.mode
13044
13304
  });
13305
+ new PrismaInstrumentation({
13306
+ enabled: true,
13307
+ mode: this.mode
13308
+ });
13045
13309
  }
13046
13310
  initializeTracing({ baseDirectory }) {
13047
13311
  const serviceName = this.config.service?.name || "unknown";
@@ -13076,7 +13340,7 @@ var TuskDriftCore = class TuskDriftCore {
13076
13340
  logLevel: initParams.logLevel || "info",
13077
13341
  prefix: "TuskDrift"
13078
13342
  });
13079
- this.samplingRate = this.config.recording?.sampling_rate ?? 1;
13343
+ this.samplingRate = this.determineSamplingRate(initParams);
13080
13344
  this.initParams = initParams;
13081
13345
  if (!this.initParams.env) {
13082
13346
  const nodeEnv = OriginalGlobalUtils.getOriginalProcessEnvVar("NODE_ENV") || "development";