@use-tusk/drift-node-sdk 0.1.11 → 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
@@ -278,7 +278,7 @@ var TdInstrumentationAbstract = class {
278
278
 
279
279
  //#endregion
280
280
  //#region package.json
281
- var version = "0.1.11";
281
+ var version = "0.1.12";
282
282
 
283
283
  //#endregion
284
284
  //#region src/version.ts
@@ -2060,7 +2060,7 @@ var HttpReplayHooks = class {
2060
2060
  auth: requestOptions.auth || void 0,
2061
2061
  agent: requestOptions.agent || void 0,
2062
2062
  protocol,
2063
- hostname: requestOptions.hostname || void 0,
2063
+ hostname: requestOptions.hostname || requestOptions.host || void 0,
2064
2064
  port: requestOptions.port ? Number(requestOptions.port) : void 0,
2065
2065
  method
2066
2066
  };
@@ -3029,13 +3029,29 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
3029
3029
  readable: res.readable
3030
3030
  };
3031
3031
  const responseChunks = [];
3032
- 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", () => {
3033
3043
  res.on("data", (chunk) => {
3034
- responseChunks.push(chunk);
3044
+ if (chunk && (streamConsumptionMode === "PIPE" || streamConsumptionMode === "NOT_CONSUMING")) {
3045
+ streamConsumptionMode = "PIPE";
3046
+ responseChunks.push(Buffer.from(chunk));
3047
+ }
3035
3048
  });
3036
- res.on("end", async () => {
3037
- if (responseChunks.length > 0) try {
3038
- 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);
3039
3055
  const rawHeaders = this._captureHeadersFromRawHeaders(res.rawHeaders);
3040
3056
  outputValue.headers = rawHeaders;
3041
3057
  const contentEncoding = rawHeaders["content-encoding"];
@@ -3044,40 +3060,24 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
3044
3060
  contentEncoding
3045
3061
  });
3046
3062
  outputValue.bodySize = responseBuffer.length;
3047
- this._addOutputAttributesToSpan({
3048
- spanInfo,
3049
- outputValue,
3050
- statusCode: res.statusCode || 1,
3051
- outputSchemaMerges: {
3052
- body: {
3053
- encoding: __use_tusk_drift_schemas_core_json_schema.EncodingType.BASE64,
3054
- decodedType: getDecodedType(outputValue.headers["content-type"] || "")
3055
- },
3056
- headers: { matchImportance: 0 }
3057
- },
3058
- inputValue: completeInputValue
3059
- });
3060
- } catch (error) {
3061
- logger.error(`[HttpInstrumentation] Error processing response body:`, error);
3062
3063
  }
3063
- });
3064
- } else try {
3065
- this._addOutputAttributesToSpan({
3066
- spanInfo,
3067
- outputValue,
3068
- statusCode: res.statusCode || 1,
3069
- outputSchemaMerges: {
3070
- body: {
3071
- encoding: __use_tusk_drift_schemas_core_json_schema.EncodingType.BASE64,
3072
- 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 }
3073
3074
  },
3074
- headers: { matchImportance: 0 }
3075
- },
3076
- inputValue: completeInputValue
3077
- });
3078
- } catch (error) {
3079
- logger.error(`[HttpInstrumentation] Error adding output attributes to span:`, error);
3080
- }
3075
+ inputValue: completeInputValue
3076
+ });
3077
+ } catch (error) {
3078
+ logger.error(`[HttpInstrumentation] Error processing response body:`, error);
3079
+ }
3080
+ });
3081
3081
  });
3082
3082
  req.on("error", (error) => {
3083
3083
  try {
@@ -9940,6 +9940,239 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
9940
9940
  }
9941
9941
  };
9942
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
+
9943
10176
  //#endregion
9944
10177
  //#region node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js
9945
10178
  var require_suppress_tracing = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js": ((exports) => {
@@ -13069,6 +13302,10 @@ var TuskDriftCore = class TuskDriftCore {
13069
13302
  enabled: true,
13070
13303
  mode: this.mode
13071
13304
  });
13305
+ new PrismaInstrumentation({
13306
+ enabled: true,
13307
+ mode: this.mode
13308
+ });
13072
13309
  }
13073
13310
  initializeTracing({ baseDirectory }) {
13074
13311
  const serviceName = this.config.service?.name || "unknown";