@prisma/client-engine-runtime 6.7.0-dev.1 → 6.7.0-dev.10

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.
@@ -0,0 +1,16 @@
1
+ export declare class UserFacingError extends Error {
2
+ name: string;
3
+ code: string;
4
+ meta: unknown;
5
+ constructor(message: string, code: string, meta?: unknown);
6
+ toQueryResponseErrorObject(): {
7
+ error: string;
8
+ user_facing_error: {
9
+ is_panic: boolean;
10
+ message: string;
11
+ meta: unknown;
12
+ error_code: string;
13
+ };
14
+ };
15
+ }
16
+ export declare function rethrowAsUserFacing(error: any): never;
package/dist/index.d.mts CHANGED
@@ -1,8 +1,15 @@
1
+ import type { Context } from '@opentelemetry/api';
1
2
  import type { IsolationLevel } from '@prisma/driver-adapter-utils';
3
+ import type { Span } from '@opentelemetry/api';
4
+ import type { SpanOptions } from '@opentelemetry/api';
2
5
  import { SqlDriverAdapter } from '@prisma/driver-adapter-utils';
3
6
  import { SqlQueryable } from '@prisma/driver-adapter-utils';
4
7
  import { Transaction } from '@prisma/driver-adapter-utils';
5
8
 
9
+ declare type ExtendedSpanOptions = SpanOptions & {
10
+ name: string;
11
+ };
12
+
6
13
  export declare type Fragment = {
7
14
  type: 'stringChunk';
8
15
  value: string;
@@ -22,6 +29,8 @@ export declare type JoinExpression = {
22
29
  parentField: string;
23
30
  };
24
31
 
32
+ export declare const noopTracingHelper: TracingHelper;
33
+
25
34
  export declare interface PlaceholderFormat {
26
35
  prefix: string;
27
36
  hasNumbering: boolean;
@@ -54,7 +63,7 @@ export declare type QueryEvent = {
54
63
 
55
64
  export declare class QueryInterpreter {
56
65
  #private;
57
- constructor({ transactionManager, placeholderValues, onQuery }: QueryInterpreterOptions);
66
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }: QueryInterpreterOptions);
58
67
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
59
68
  private interpretNode;
60
69
  }
@@ -63,6 +72,7 @@ export declare type QueryInterpreterOptions = {
63
72
  transactionManager: QueryInterpreterTransactionManager;
64
73
  placeholderValues: Record<string, unknown>;
65
74
  onQuery?: (event: QueryEvent) => void;
75
+ tracingHelper: TracingHelper;
66
76
  };
67
77
 
68
78
  export declare type QueryInterpreterTransactionManager = {
@@ -145,18 +155,27 @@ export declare type QueryPlanNode = {
145
155
  args: QueryPlanNode;
146
156
  };
147
157
 
158
+ declare type SpanCallback<R> = (span?: Span, context?: Context) => R;
159
+
160
+ export declare interface TracingHelper {
161
+ runInChildSpan<R>(nameOrOptions: string | ExtendedSpanOptions, callback: SpanCallback<R>): R;
162
+ }
163
+
148
164
  export declare type TransactionInfo = {
149
165
  id: string;
150
166
  };
151
167
 
152
168
  export declare class TransactionManager {
169
+ #private;
153
170
  private transactions;
154
171
  private closedTransactions;
155
172
  private readonly driverAdapter;
156
173
  private readonly transactionOptions;
157
- constructor({ driverAdapter, transactionOptions }: {
174
+ private readonly tracingHelper;
175
+ constructor({ driverAdapter, transactionOptions, tracingHelper, }: {
158
176
  driverAdapter: SqlDriverAdapter;
159
177
  transactionOptions: TransactionOptions;
178
+ tracingHelper: TracingHelper;
160
179
  });
161
180
  startTransaction(options?: TransactionOptions): Promise<TransactionInfo>;
162
181
  commitTransaction(transactionId: string): Promise<void>;
@@ -181,4 +200,20 @@ export declare type TransactionOptions = {
181
200
  isolationLevel?: IsolationLevel;
182
201
  };
183
202
 
203
+ export declare class UserFacingError extends Error {
204
+ name: string;
205
+ code: string;
206
+ meta: unknown;
207
+ constructor(message: string, code: string, meta?: unknown);
208
+ toQueryResponseErrorObject(): {
209
+ error: string;
210
+ user_facing_error: {
211
+ is_panic: boolean;
212
+ message: string;
213
+ meta: unknown;
214
+ error_code: string;
215
+ };
216
+ };
217
+ }
218
+
184
219
  export { }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,15 @@
1
+ import type { Context } from '@opentelemetry/api';
1
2
  import type { IsolationLevel } from '@prisma/driver-adapter-utils';
3
+ import type { Span } from '@opentelemetry/api';
4
+ import type { SpanOptions } from '@opentelemetry/api';
2
5
  import { SqlDriverAdapter } from '@prisma/driver-adapter-utils';
3
6
  import { SqlQueryable } from '@prisma/driver-adapter-utils';
4
7
  import { Transaction } from '@prisma/driver-adapter-utils';
5
8
 
9
+ declare type ExtendedSpanOptions = SpanOptions & {
10
+ name: string;
11
+ };
12
+
6
13
  export declare type Fragment = {
7
14
  type: 'stringChunk';
8
15
  value: string;
@@ -22,6 +29,8 @@ export declare type JoinExpression = {
22
29
  parentField: string;
23
30
  };
24
31
 
32
+ export declare const noopTracingHelper: TracingHelper;
33
+
25
34
  export declare interface PlaceholderFormat {
26
35
  prefix: string;
27
36
  hasNumbering: boolean;
@@ -54,7 +63,7 @@ export declare type QueryEvent = {
54
63
 
55
64
  export declare class QueryInterpreter {
56
65
  #private;
57
- constructor({ transactionManager, placeholderValues, onQuery }: QueryInterpreterOptions);
66
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }: QueryInterpreterOptions);
58
67
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
59
68
  private interpretNode;
60
69
  }
@@ -63,6 +72,7 @@ export declare type QueryInterpreterOptions = {
63
72
  transactionManager: QueryInterpreterTransactionManager;
64
73
  placeholderValues: Record<string, unknown>;
65
74
  onQuery?: (event: QueryEvent) => void;
75
+ tracingHelper: TracingHelper;
66
76
  };
67
77
 
68
78
  export declare type QueryInterpreterTransactionManager = {
@@ -145,18 +155,27 @@ export declare type QueryPlanNode = {
145
155
  args: QueryPlanNode;
146
156
  };
147
157
 
158
+ declare type SpanCallback<R> = (span?: Span, context?: Context) => R;
159
+
160
+ export declare interface TracingHelper {
161
+ runInChildSpan<R>(nameOrOptions: string | ExtendedSpanOptions, callback: SpanCallback<R>): R;
162
+ }
163
+
148
164
  export declare type TransactionInfo = {
149
165
  id: string;
150
166
  };
151
167
 
152
168
  export declare class TransactionManager {
169
+ #private;
153
170
  private transactions;
154
171
  private closedTransactions;
155
172
  private readonly driverAdapter;
156
173
  private readonly transactionOptions;
157
- constructor({ driverAdapter, transactionOptions }: {
174
+ private readonly tracingHelper;
175
+ constructor({ driverAdapter, transactionOptions, tracingHelper, }: {
158
176
  driverAdapter: SqlDriverAdapter;
159
177
  transactionOptions: TransactionOptions;
178
+ tracingHelper: TracingHelper;
160
179
  });
161
180
  startTransaction(options?: TransactionOptions): Promise<TransactionInfo>;
162
181
  commitTransaction(transactionId: string): Promise<void>;
@@ -181,4 +200,20 @@ export declare type TransactionOptions = {
181
200
  isolationLevel?: IsolationLevel;
182
201
  };
183
202
 
203
+ export declare class UserFacingError extends Error {
204
+ name: string;
205
+ code: string;
206
+ meta: unknown;
207
+ constructor(message: string, code: string, meta?: unknown);
208
+ toQueryResponseErrorObject(): {
209
+ error: string;
210
+ user_facing_error: {
211
+ is_panic: boolean;
212
+ message: string;
213
+ meta: unknown;
214
+ error_code: string;
215
+ };
216
+ };
217
+ }
218
+
184
219
  export { }
package/dist/index.js CHANGED
@@ -33,11 +33,183 @@ __export(index_exports, {
33
33
  QueryInterpreter: () => QueryInterpreter,
34
34
  TransactionManager: () => TransactionManager,
35
35
  TransactionManagerError: () => TransactionManagerError,
36
+ UserFacingError: () => UserFacingError,
36
37
  isPrismaValueGenerator: () => isPrismaValueGenerator,
37
- isPrismaValuePlaceholder: () => isPrismaValuePlaceholder
38
+ isPrismaValuePlaceholder: () => isPrismaValuePlaceholder,
39
+ noopTracingHelper: () => noopTracingHelper
38
40
  });
39
41
  module.exports = __toCommonJS(index_exports);
40
42
 
43
+ // src/interpreter/QueryInterpreter.ts
44
+ var import_api = require("@opentelemetry/api");
45
+
46
+ // src/utils.ts
47
+ function assertNever(_, message) {
48
+ throw new Error(message);
49
+ }
50
+
51
+ // src/tracing.ts
52
+ var noopTracingHelper = {
53
+ runInChildSpan(_, callback) {
54
+ return callback();
55
+ }
56
+ };
57
+ function providerToOtelSystem(provider) {
58
+ switch (provider) {
59
+ case "postgres":
60
+ return "postgresql";
61
+ case "mysql":
62
+ return "mysql";
63
+ case "sqlite":
64
+ return "sqlite";
65
+ default:
66
+ assertNever(provider, `Unknown provider: ${provider}`);
67
+ }
68
+ }
69
+
70
+ // src/UserFacingError.ts
71
+ var import_driver_adapter_utils = require("@prisma/driver-adapter-utils");
72
+ var UserFacingError = class extends Error {
73
+ name = "UserFacingError";
74
+ code;
75
+ meta;
76
+ constructor(message, code, meta) {
77
+ super(message);
78
+ this.code = code;
79
+ this.meta = meta;
80
+ }
81
+ toQueryResponseErrorObject() {
82
+ return {
83
+ error: this.message,
84
+ user_facing_error: {
85
+ is_panic: false,
86
+ message: this.message,
87
+ meta: this.meta,
88
+ error_code: this.code
89
+ }
90
+ };
91
+ }
92
+ };
93
+ function rethrowAsUserFacing(error) {
94
+ if (!(0, import_driver_adapter_utils.isDriverAdapterError)(error)) {
95
+ throw error;
96
+ }
97
+ const code = getErrorCode(error);
98
+ const message = renderErrorMessage(error);
99
+ if (!code || !message) {
100
+ throw error;
101
+ }
102
+ throw new UserFacingError(message, code, error);
103
+ }
104
+ function getErrorCode(err) {
105
+ switch (err.cause.kind) {
106
+ case "AuthenticationFailed":
107
+ return "P1000";
108
+ case "DatabaseDoesNotExist":
109
+ return "P1003";
110
+ case "SocketTimeout":
111
+ return "P1008";
112
+ case "DatabaseAlreadyExists":
113
+ return "P1009";
114
+ case "DatabaseAccessDenied":
115
+ return "P1010";
116
+ case "LengthMismatch":
117
+ return "P2000";
118
+ case "UniqueConstraintViolation":
119
+ return "P2002";
120
+ case "ForeignKeyConstraintViolation":
121
+ return "P2003";
122
+ case "UnsupportedNativeDataType":
123
+ return "P2010";
124
+ case "NullConstraintViolation":
125
+ return "P2011";
126
+ case "TableDoesNotExist":
127
+ return "P2021";
128
+ case "ColumnNotFound":
129
+ return "P2022";
130
+ case "InvalidIsolationLevel":
131
+ return "P2023";
132
+ case "TransactionWriteConflict":
133
+ return "P2034";
134
+ case "GenericJs":
135
+ return "P2036";
136
+ case "TooManyConnections":
137
+ return "P2037";
138
+ case "postgres":
139
+ case "sqlite":
140
+ case "mysql":
141
+ return;
142
+ default:
143
+ assertNever(err.cause, `Unknown error: ${err.cause}`);
144
+ }
145
+ }
146
+ function renderErrorMessage(err) {
147
+ switch (err.cause.kind) {
148
+ case "AuthenticationFailed": {
149
+ const user = err.cause.user ?? "(not available)";
150
+ return `Authentication failed against the database server, the provided database credentials for \`${user}\` are not valid`;
151
+ }
152
+ case "DatabaseDoesNotExist": {
153
+ const db = err.cause.db ?? "(not available)";
154
+ return `Database \`${db}\` does not exist on the database server`;
155
+ }
156
+ case "SocketTimeout":
157
+ return `Operation has timed out`;
158
+ case "DatabaseAlreadyExists": {
159
+ const db = err.cause.db ?? "(not available)";
160
+ return `Database \`${db}\` already exists on the database server`;
161
+ }
162
+ case "DatabaseAccessDenied": {
163
+ const db = err.cause.db ?? "(not available)";
164
+ return `User was denied access on the database \`${db}\``;
165
+ }
166
+ case "LengthMismatch": {
167
+ const column = err.cause.column ?? "(not available)";
168
+ return `The provided value for the column is too long for the column's type. Column: ${column}`;
169
+ }
170
+ case "UniqueConstraintViolation":
171
+ return `Unique constraint failed on the ${renderConstraint({ fields: err.cause.fields })}`;
172
+ case "ForeignKeyConstraintViolation":
173
+ return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
174
+ case "UnsupportedNativeDataType":
175
+ return `Failed to deserialize column of type '${err.cause.type}'. If you're using $queryRaw and this column is explicitly marked as \`Unsupported\` in your Prisma schema, try casting this column to any supported Prisma type such as \`String\`.`;
176
+ case "NullConstraintViolation":
177
+ return `Null constraint violation on the ${renderConstraint({ fields: err.cause.fields })}`;
178
+ case "TableDoesNotExist": {
179
+ const table = err.cause.table ?? "(not available)";
180
+ return `The table \`${table}\` does not exist in the current database.`;
181
+ }
182
+ case "ColumnNotFound": {
183
+ const column = err.cause.column ?? "(not available)";
184
+ return `The column \`${column}\` does not exist in the current database.`;
185
+ }
186
+ case "InvalidIsolationLevel":
187
+ return `Invalid isolation level \`${err.cause.level}\``;
188
+ case "TransactionWriteConflict":
189
+ return `Transaction failed due to a write conflict or a deadlock. Please retry your transaction`;
190
+ case "GenericJs":
191
+ return `Error in external connector (id ${err.cause.id})`;
192
+ case "TooManyConnections":
193
+ return `Too many database connections opened: ${err.cause.cause}`;
194
+ case "sqlite":
195
+ case "postgres":
196
+ case "mysql":
197
+ return;
198
+ default:
199
+ assertNever(err.cause, `Unknown error: ${err.cause}`);
200
+ }
201
+ }
202
+ function renderConstraint(constraint) {
203
+ if (constraint && "fields" in constraint) {
204
+ return `fields: (${constraint.fields.map((field) => `\`${field}\``).join(", ")})`;
205
+ } else if (constraint && "index" in constraint) {
206
+ return `constraint: \`${constraint.index}\``;
207
+ } else if (constraint && "foreignKey" in constraint) {
208
+ return `foreign key`;
209
+ }
210
+ return "(not available)";
211
+ }
212
+
41
213
  // src/interpreter/generators.ts
42
214
  var import_cuid = __toESM(require("@bugsnag/cuid"));
43
215
  var import_cuid2 = require("@paralleldrive/cuid2");
@@ -123,11 +295,6 @@ function isPrismaValueGenerator(value) {
123
295
  return typeof value === "object" && value !== null && value["prisma__type"] === "generatorCall";
124
296
  }
125
297
 
126
- // src/utils.ts
127
- function assertNever(_, message) {
128
- throw new Error(message);
129
- }
130
-
131
298
  // src/interpreter/renderQuery.ts
132
299
  function renderQuery(dbQuery, scope, generators) {
133
300
  const queryType = dbQuery.type;
@@ -278,13 +445,17 @@ var QueryInterpreter = class {
278
445
  #placeholderValues;
279
446
  #onQuery;
280
447
  #generators = new GeneratorRegistry();
281
- constructor({ transactionManager, placeholderValues, onQuery }) {
448
+ #tracingHelper;
449
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }) {
282
450
  this.#transactionManager = transactionManager;
283
451
  this.#placeholderValues = placeholderValues;
284
452
  this.#onQuery = onQuery;
453
+ this.#tracingHelper = tracingHelper;
285
454
  }
286
455
  async run(queryPlan, queryable) {
287
- return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot());
456
+ return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot()).catch(
457
+ (e) => rethrowAsUserFacing(e)
458
+ );
288
459
  }
289
460
  async interpretNode(node, queryable, scope, generators) {
290
461
  switch (node.type) {
@@ -323,13 +494,13 @@ var QueryInterpreter = class {
323
494
  }
324
495
  case "execute": {
325
496
  const query = renderQuery(node.args, scope, generators);
326
- return this.#withQueryEvent(query, async () => {
497
+ return this.#withQueryEvent(query, queryable, async () => {
327
498
  return await queryable.executeRaw(query);
328
499
  });
329
500
  }
330
501
  case "query": {
331
502
  const query = renderQuery(node.args, scope, generators);
332
- return this.#withQueryEvent(query, async () => {
503
+ return this.#withQueryEvent(query, queryable, async () => {
333
504
  return serialize(await queryable.queryRaw(query));
334
505
  });
335
506
  }
@@ -390,24 +561,34 @@ var QueryInterpreter = class {
390
561
  throw e;
391
562
  }
392
563
  }
393
- default: {
394
- node;
395
- throw new Error(`Unexpected node type: ${node.type}`);
396
- }
564
+ default:
565
+ assertNever(node, `Unexpected node type: ${node.type}`);
397
566
  }
398
567
  }
399
- async #withQueryEvent(query, execute) {
400
- const timestamp = /* @__PURE__ */ new Date();
401
- const startInstant = performance.now();
402
- const result = await execute();
403
- const endInstant = performance.now();
404
- this.#onQuery?.({
405
- timestamp,
406
- duration: endInstant - startInstant,
407
- query: query.sql,
408
- params: query.args
409
- });
410
- return result;
568
+ #withQueryEvent(query, queryable, execute) {
569
+ return this.#tracingHelper.runInChildSpan(
570
+ {
571
+ name: "db_query",
572
+ kind: import_api.SpanKind.CLIENT,
573
+ attributes: {
574
+ "db.query.text": query.sql,
575
+ "db.system.name": providerToOtelSystem(queryable.provider)
576
+ }
577
+ },
578
+ async () => {
579
+ const timestamp = /* @__PURE__ */ new Date();
580
+ const startInstant = performance.now();
581
+ const result = await execute();
582
+ const endInstant = performance.now();
583
+ this.#onQuery?.({
584
+ timestamp,
585
+ duration: endInstant - startInstant,
586
+ query: query.sql,
587
+ params: query.args
588
+ });
589
+ return result;
590
+ }
591
+ );
411
592
  }
412
593
  };
413
594
  function isEmpty(value) {
@@ -486,11 +667,6 @@ var TransactionManagerError = class extends Error {
486
667
  }
487
668
  code = "P2028";
488
669
  };
489
- var TransactionDriverAdapterError = class extends TransactionManagerError {
490
- constructor(message, errorParams) {
491
- super(`Error from Driver Adapter: ${message}`, { ...errorParams.driverAdapterError });
492
- }
493
- };
494
670
  var TransactionNotFoundError = class extends TransactionManagerError {
495
671
  constructor() {
496
672
  super(
@@ -508,7 +684,7 @@ var TransactionRolledBackError = class extends TransactionManagerError {
508
684
  super(`Transaction already closed: A ${operation} cannot be executed on a committed transaction`);
509
685
  }
510
686
  };
511
- var TransactionStartTimoutError = class extends TransactionManagerError {
687
+ var TransactionStartTimeoutError = class extends TransactionManagerError {
512
688
  constructor() {
513
689
  super("Unable to start a transaction in the given time.");
514
690
  }
@@ -545,11 +721,20 @@ var TransactionManager = class {
545
721
  closedTransactions = [];
546
722
  driverAdapter;
547
723
  transactionOptions;
548
- constructor({ driverAdapter, transactionOptions }) {
724
+ tracingHelper;
725
+ constructor({
726
+ driverAdapter,
727
+ transactionOptions,
728
+ tracingHelper
729
+ }) {
549
730
  this.driverAdapter = driverAdapter;
550
731
  this.transactionOptions = transactionOptions;
732
+ this.tracingHelper = tracingHelper;
551
733
  }
552
734
  async startTransaction(options) {
735
+ return await this.tracingHelper.runInChildSpan("start_transaction", () => this.#startTransactionImpl(options));
736
+ }
737
+ async #startTransactionImpl(options) {
553
738
  const validatedOptions = options !== void 0 ? this.validateOptions(options) : this.transactionOptions;
554
739
  const transaction = {
555
740
  id: await randomUUID(),
@@ -561,14 +746,7 @@ var TransactionManager = class {
561
746
  };
562
747
  this.transactions.set(transaction.id, transaction);
563
748
  transaction.timer = this.startTransactionTimeout(transaction.id, validatedOptions.maxWait);
564
- let startedTransaction;
565
- try {
566
- startedTransaction = await this.driverAdapter.startTransaction(validatedOptions.isolationLevel);
567
- } catch (error) {
568
- throw new TransactionDriverAdapterError("Failed to start transaction.", {
569
- driverAdapterError: error
570
- });
571
- }
749
+ const startedTransaction = await this.driverAdapter.startTransaction(validatedOptions.isolationLevel);
572
750
  switch (transaction.status) {
573
751
  case "waiting":
574
752
  transaction.transaction = startedTransaction;
@@ -578,7 +756,7 @@ var TransactionManager = class {
578
756
  transaction.timer = this.startTransactionTimeout(transaction.id, validatedOptions.timeout);
579
757
  return { id: transaction.id };
580
758
  case "timed_out":
581
- throw new TransactionStartTimoutError();
759
+ throw new TransactionStartTimeoutError();
582
760
  case "running":
583
761
  case "committed":
584
762
  case "rolled_back":
@@ -590,12 +768,16 @@ var TransactionManager = class {
590
768
  }
591
769
  }
592
770
  async commitTransaction(transactionId) {
593
- const txw = this.getActiveTransaction(transactionId, "commit");
594
- await this.closeTransaction(txw, "committed");
771
+ return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
772
+ const txw = this.getActiveTransaction(transactionId, "commit");
773
+ await this.closeTransaction(txw, "committed");
774
+ });
595
775
  }
596
776
  async rollbackTransaction(transactionId) {
597
- const txw = this.getActiveTransaction(transactionId, "rollback");
598
- await this.closeTransaction(txw, "rolled_back");
777
+ return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
778
+ const txw = this.getActiveTransaction(transactionId, "rollback");
779
+ await this.closeTransaction(txw, "rolled_back");
780
+ });
599
781
  }
600
782
  getTransaction(txInfo, operation) {
601
783
  const tx = this.getActiveTransaction(txInfo.id, operation);
@@ -651,24 +833,12 @@ var TransactionManager = class {
651
833
  debug("Closing transaction.", { transactionId: tx.id, status });
652
834
  tx.status = status;
653
835
  if (tx.transaction && status === "committed") {
654
- try {
655
- await tx.transaction.commit();
656
- } catch (error) {
657
- throw new TransactionDriverAdapterError("Failed to commit transaction.", {
658
- driverAdapterError: error
659
- });
660
- }
836
+ await tx.transaction.commit();
661
837
  if (!tx.transaction.options.usePhantomQuery) {
662
838
  await tx.transaction.executeRaw(COMMIT_QUERY());
663
839
  }
664
840
  } else if (tx.transaction) {
665
- try {
666
- await tx.transaction.rollback();
667
- } catch (error) {
668
- throw new TransactionDriverAdapterError("Failed to rollback transaction.", {
669
- driverAdapterError: error
670
- });
671
- }
841
+ await tx.transaction.rollback();
672
842
  if (!tx.transaction.options.usePhantomQuery) {
673
843
  await tx.transaction.executeRaw(ROLLBACK_QUERY());
674
844
  }
@@ -697,6 +867,8 @@ var TransactionManager = class {
697
867
  QueryInterpreter,
698
868
  TransactionManager,
699
869
  TransactionManagerError,
870
+ UserFacingError,
700
871
  isPrismaValueGenerator,
701
- isPrismaValuePlaceholder
872
+ isPrismaValuePlaceholder,
873
+ noopTracingHelper
702
874
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,173 @@
1
+ // src/interpreter/QueryInterpreter.ts
2
+ import { SpanKind } from "@opentelemetry/api";
3
+
4
+ // src/utils.ts
5
+ function assertNever(_, message) {
6
+ throw new Error(message);
7
+ }
8
+
9
+ // src/tracing.ts
10
+ var noopTracingHelper = {
11
+ runInChildSpan(_, callback) {
12
+ return callback();
13
+ }
14
+ };
15
+ function providerToOtelSystem(provider) {
16
+ switch (provider) {
17
+ case "postgres":
18
+ return "postgresql";
19
+ case "mysql":
20
+ return "mysql";
21
+ case "sqlite":
22
+ return "sqlite";
23
+ default:
24
+ assertNever(provider, `Unknown provider: ${provider}`);
25
+ }
26
+ }
27
+
28
+ // src/UserFacingError.ts
29
+ import { isDriverAdapterError } from "@prisma/driver-adapter-utils";
30
+ var UserFacingError = class extends Error {
31
+ name = "UserFacingError";
32
+ code;
33
+ meta;
34
+ constructor(message, code, meta) {
35
+ super(message);
36
+ this.code = code;
37
+ this.meta = meta;
38
+ }
39
+ toQueryResponseErrorObject() {
40
+ return {
41
+ error: this.message,
42
+ user_facing_error: {
43
+ is_panic: false,
44
+ message: this.message,
45
+ meta: this.meta,
46
+ error_code: this.code
47
+ }
48
+ };
49
+ }
50
+ };
51
+ function rethrowAsUserFacing(error) {
52
+ if (!isDriverAdapterError(error)) {
53
+ throw error;
54
+ }
55
+ const code = getErrorCode(error);
56
+ const message = renderErrorMessage(error);
57
+ if (!code || !message) {
58
+ throw error;
59
+ }
60
+ throw new UserFacingError(message, code, error);
61
+ }
62
+ function getErrorCode(err) {
63
+ switch (err.cause.kind) {
64
+ case "AuthenticationFailed":
65
+ return "P1000";
66
+ case "DatabaseDoesNotExist":
67
+ return "P1003";
68
+ case "SocketTimeout":
69
+ return "P1008";
70
+ case "DatabaseAlreadyExists":
71
+ return "P1009";
72
+ case "DatabaseAccessDenied":
73
+ return "P1010";
74
+ case "LengthMismatch":
75
+ return "P2000";
76
+ case "UniqueConstraintViolation":
77
+ return "P2002";
78
+ case "ForeignKeyConstraintViolation":
79
+ return "P2003";
80
+ case "UnsupportedNativeDataType":
81
+ return "P2010";
82
+ case "NullConstraintViolation":
83
+ return "P2011";
84
+ case "TableDoesNotExist":
85
+ return "P2021";
86
+ case "ColumnNotFound":
87
+ return "P2022";
88
+ case "InvalidIsolationLevel":
89
+ return "P2023";
90
+ case "TransactionWriteConflict":
91
+ return "P2034";
92
+ case "GenericJs":
93
+ return "P2036";
94
+ case "TooManyConnections":
95
+ return "P2037";
96
+ case "postgres":
97
+ case "sqlite":
98
+ case "mysql":
99
+ return;
100
+ default:
101
+ assertNever(err.cause, `Unknown error: ${err.cause}`);
102
+ }
103
+ }
104
+ function renderErrorMessage(err) {
105
+ switch (err.cause.kind) {
106
+ case "AuthenticationFailed": {
107
+ const user = err.cause.user ?? "(not available)";
108
+ return `Authentication failed against the database server, the provided database credentials for \`${user}\` are not valid`;
109
+ }
110
+ case "DatabaseDoesNotExist": {
111
+ const db = err.cause.db ?? "(not available)";
112
+ return `Database \`${db}\` does not exist on the database server`;
113
+ }
114
+ case "SocketTimeout":
115
+ return `Operation has timed out`;
116
+ case "DatabaseAlreadyExists": {
117
+ const db = err.cause.db ?? "(not available)";
118
+ return `Database \`${db}\` already exists on the database server`;
119
+ }
120
+ case "DatabaseAccessDenied": {
121
+ const db = err.cause.db ?? "(not available)";
122
+ return `User was denied access on the database \`${db}\``;
123
+ }
124
+ case "LengthMismatch": {
125
+ const column = err.cause.column ?? "(not available)";
126
+ return `The provided value for the column is too long for the column's type. Column: ${column}`;
127
+ }
128
+ case "UniqueConstraintViolation":
129
+ return `Unique constraint failed on the ${renderConstraint({ fields: err.cause.fields })}`;
130
+ case "ForeignKeyConstraintViolation":
131
+ return `Foreign key constraint violated on the ${renderConstraint(err.cause.constraint)}`;
132
+ case "UnsupportedNativeDataType":
133
+ return `Failed to deserialize column of type '${err.cause.type}'. If you're using $queryRaw and this column is explicitly marked as \`Unsupported\` in your Prisma schema, try casting this column to any supported Prisma type such as \`String\`.`;
134
+ case "NullConstraintViolation":
135
+ return `Null constraint violation on the ${renderConstraint({ fields: err.cause.fields })}`;
136
+ case "TableDoesNotExist": {
137
+ const table = err.cause.table ?? "(not available)";
138
+ return `The table \`${table}\` does not exist in the current database.`;
139
+ }
140
+ case "ColumnNotFound": {
141
+ const column = err.cause.column ?? "(not available)";
142
+ return `The column \`${column}\` does not exist in the current database.`;
143
+ }
144
+ case "InvalidIsolationLevel":
145
+ return `Invalid isolation level \`${err.cause.level}\``;
146
+ case "TransactionWriteConflict":
147
+ return `Transaction failed due to a write conflict or a deadlock. Please retry your transaction`;
148
+ case "GenericJs":
149
+ return `Error in external connector (id ${err.cause.id})`;
150
+ case "TooManyConnections":
151
+ return `Too many database connections opened: ${err.cause.cause}`;
152
+ case "sqlite":
153
+ case "postgres":
154
+ case "mysql":
155
+ return;
156
+ default:
157
+ assertNever(err.cause, `Unknown error: ${err.cause}`);
158
+ }
159
+ }
160
+ function renderConstraint(constraint) {
161
+ if (constraint && "fields" in constraint) {
162
+ return `fields: (${constraint.fields.map((field) => `\`${field}\``).join(", ")})`;
163
+ } else if (constraint && "index" in constraint) {
164
+ return `constraint: \`${constraint.index}\``;
165
+ } else if (constraint && "foreignKey" in constraint) {
166
+ return `foreign key`;
167
+ }
168
+ return "(not available)";
169
+ }
170
+
1
171
  // src/interpreter/generators.ts
2
172
  import cuid1 from "@bugsnag/cuid";
3
173
  import { createId as cuid2 } from "@paralleldrive/cuid2";
@@ -83,11 +253,6 @@ function isPrismaValueGenerator(value) {
83
253
  return typeof value === "object" && value !== null && value["prisma__type"] === "generatorCall";
84
254
  }
85
255
 
86
- // src/utils.ts
87
- function assertNever(_, message) {
88
- throw new Error(message);
89
- }
90
-
91
256
  // src/interpreter/renderQuery.ts
92
257
  function renderQuery(dbQuery, scope, generators) {
93
258
  const queryType = dbQuery.type;
@@ -238,13 +403,17 @@ var QueryInterpreter = class {
238
403
  #placeholderValues;
239
404
  #onQuery;
240
405
  #generators = new GeneratorRegistry();
241
- constructor({ transactionManager, placeholderValues, onQuery }) {
406
+ #tracingHelper;
407
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }) {
242
408
  this.#transactionManager = transactionManager;
243
409
  this.#placeholderValues = placeholderValues;
244
410
  this.#onQuery = onQuery;
411
+ this.#tracingHelper = tracingHelper;
245
412
  }
246
413
  async run(queryPlan, queryable) {
247
- return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot());
414
+ return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot()).catch(
415
+ (e) => rethrowAsUserFacing(e)
416
+ );
248
417
  }
249
418
  async interpretNode(node, queryable, scope, generators) {
250
419
  switch (node.type) {
@@ -283,13 +452,13 @@ var QueryInterpreter = class {
283
452
  }
284
453
  case "execute": {
285
454
  const query = renderQuery(node.args, scope, generators);
286
- return this.#withQueryEvent(query, async () => {
455
+ return this.#withQueryEvent(query, queryable, async () => {
287
456
  return await queryable.executeRaw(query);
288
457
  });
289
458
  }
290
459
  case "query": {
291
460
  const query = renderQuery(node.args, scope, generators);
292
- return this.#withQueryEvent(query, async () => {
461
+ return this.#withQueryEvent(query, queryable, async () => {
293
462
  return serialize(await queryable.queryRaw(query));
294
463
  });
295
464
  }
@@ -350,24 +519,34 @@ var QueryInterpreter = class {
350
519
  throw e;
351
520
  }
352
521
  }
353
- default: {
354
- node;
355
- throw new Error(`Unexpected node type: ${node.type}`);
356
- }
522
+ default:
523
+ assertNever(node, `Unexpected node type: ${node.type}`);
357
524
  }
358
525
  }
359
- async #withQueryEvent(query, execute) {
360
- const timestamp = /* @__PURE__ */ new Date();
361
- const startInstant = performance.now();
362
- const result = await execute();
363
- const endInstant = performance.now();
364
- this.#onQuery?.({
365
- timestamp,
366
- duration: endInstant - startInstant,
367
- query: query.sql,
368
- params: query.args
369
- });
370
- return result;
526
+ #withQueryEvent(query, queryable, execute) {
527
+ return this.#tracingHelper.runInChildSpan(
528
+ {
529
+ name: "db_query",
530
+ kind: SpanKind.CLIENT,
531
+ attributes: {
532
+ "db.query.text": query.sql,
533
+ "db.system.name": providerToOtelSystem(queryable.provider)
534
+ }
535
+ },
536
+ async () => {
537
+ const timestamp = /* @__PURE__ */ new Date();
538
+ const startInstant = performance.now();
539
+ const result = await execute();
540
+ const endInstant = performance.now();
541
+ this.#onQuery?.({
542
+ timestamp,
543
+ duration: endInstant - startInstant,
544
+ query: query.sql,
545
+ params: query.args
546
+ });
547
+ return result;
548
+ }
549
+ );
371
550
  }
372
551
  };
373
552
  function isEmpty(value) {
@@ -446,11 +625,6 @@ var TransactionManagerError = class extends Error {
446
625
  }
447
626
  code = "P2028";
448
627
  };
449
- var TransactionDriverAdapterError = class extends TransactionManagerError {
450
- constructor(message, errorParams) {
451
- super(`Error from Driver Adapter: ${message}`, { ...errorParams.driverAdapterError });
452
- }
453
- };
454
628
  var TransactionNotFoundError = class extends TransactionManagerError {
455
629
  constructor() {
456
630
  super(
@@ -468,7 +642,7 @@ var TransactionRolledBackError = class extends TransactionManagerError {
468
642
  super(`Transaction already closed: A ${operation} cannot be executed on a committed transaction`);
469
643
  }
470
644
  };
471
- var TransactionStartTimoutError = class extends TransactionManagerError {
645
+ var TransactionStartTimeoutError = class extends TransactionManagerError {
472
646
  constructor() {
473
647
  super("Unable to start a transaction in the given time.");
474
648
  }
@@ -505,11 +679,20 @@ var TransactionManager = class {
505
679
  closedTransactions = [];
506
680
  driverAdapter;
507
681
  transactionOptions;
508
- constructor({ driverAdapter, transactionOptions }) {
682
+ tracingHelper;
683
+ constructor({
684
+ driverAdapter,
685
+ transactionOptions,
686
+ tracingHelper
687
+ }) {
509
688
  this.driverAdapter = driverAdapter;
510
689
  this.transactionOptions = transactionOptions;
690
+ this.tracingHelper = tracingHelper;
511
691
  }
512
692
  async startTransaction(options) {
693
+ return await this.tracingHelper.runInChildSpan("start_transaction", () => this.#startTransactionImpl(options));
694
+ }
695
+ async #startTransactionImpl(options) {
513
696
  const validatedOptions = options !== void 0 ? this.validateOptions(options) : this.transactionOptions;
514
697
  const transaction = {
515
698
  id: await randomUUID(),
@@ -521,14 +704,7 @@ var TransactionManager = class {
521
704
  };
522
705
  this.transactions.set(transaction.id, transaction);
523
706
  transaction.timer = this.startTransactionTimeout(transaction.id, validatedOptions.maxWait);
524
- let startedTransaction;
525
- try {
526
- startedTransaction = await this.driverAdapter.startTransaction(validatedOptions.isolationLevel);
527
- } catch (error) {
528
- throw new TransactionDriverAdapterError("Failed to start transaction.", {
529
- driverAdapterError: error
530
- });
531
- }
707
+ const startedTransaction = await this.driverAdapter.startTransaction(validatedOptions.isolationLevel);
532
708
  switch (transaction.status) {
533
709
  case "waiting":
534
710
  transaction.transaction = startedTransaction;
@@ -538,7 +714,7 @@ var TransactionManager = class {
538
714
  transaction.timer = this.startTransactionTimeout(transaction.id, validatedOptions.timeout);
539
715
  return { id: transaction.id };
540
716
  case "timed_out":
541
- throw new TransactionStartTimoutError();
717
+ throw new TransactionStartTimeoutError();
542
718
  case "running":
543
719
  case "committed":
544
720
  case "rolled_back":
@@ -550,12 +726,16 @@ var TransactionManager = class {
550
726
  }
551
727
  }
552
728
  async commitTransaction(transactionId) {
553
- const txw = this.getActiveTransaction(transactionId, "commit");
554
- await this.closeTransaction(txw, "committed");
729
+ return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
730
+ const txw = this.getActiveTransaction(transactionId, "commit");
731
+ await this.closeTransaction(txw, "committed");
732
+ });
555
733
  }
556
734
  async rollbackTransaction(transactionId) {
557
- const txw = this.getActiveTransaction(transactionId, "rollback");
558
- await this.closeTransaction(txw, "rolled_back");
735
+ return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
736
+ const txw = this.getActiveTransaction(transactionId, "rollback");
737
+ await this.closeTransaction(txw, "rolled_back");
738
+ });
559
739
  }
560
740
  getTransaction(txInfo, operation) {
561
741
  const tx = this.getActiveTransaction(txInfo.id, operation);
@@ -611,24 +791,12 @@ var TransactionManager = class {
611
791
  debug("Closing transaction.", { transactionId: tx.id, status });
612
792
  tx.status = status;
613
793
  if (tx.transaction && status === "committed") {
614
- try {
615
- await tx.transaction.commit();
616
- } catch (error) {
617
- throw new TransactionDriverAdapterError("Failed to commit transaction.", {
618
- driverAdapterError: error
619
- });
620
- }
794
+ await tx.transaction.commit();
621
795
  if (!tx.transaction.options.usePhantomQuery) {
622
796
  await tx.transaction.executeRaw(COMMIT_QUERY());
623
797
  }
624
798
  } else if (tx.transaction) {
625
- try {
626
- await tx.transaction.rollback();
627
- } catch (error) {
628
- throw new TransactionDriverAdapterError("Failed to rollback transaction.", {
629
- driverAdapterError: error
630
- });
631
- }
799
+ await tx.transaction.rollback();
632
800
  if (!tx.transaction.options.usePhantomQuery) {
633
801
  await tx.transaction.executeRaw(ROLLBACK_QUERY());
634
802
  }
@@ -656,6 +824,8 @@ export {
656
824
  QueryInterpreter,
657
825
  TransactionManager,
658
826
  TransactionManagerError,
827
+ UserFacingError,
659
828
  isPrismaValueGenerator,
660
- isPrismaValuePlaceholder
829
+ isPrismaValuePlaceholder,
830
+ noopTracingHelper
661
831
  };
@@ -1,6 +1,7 @@
1
1
  import { SqlQueryable } from '@prisma/driver-adapter-utils';
2
2
  import { QueryEvent } from '../events';
3
3
  import { QueryPlanNode } from '../QueryPlan';
4
+ import { type TracingHelper } from '../tracing';
4
5
  import { type TransactionManager } from '../transactionManager/TransactionManager';
5
6
  export type QueryInterpreterTransactionManager = {
6
7
  enabled: true;
@@ -12,10 +13,11 @@ export type QueryInterpreterOptions = {
12
13
  transactionManager: QueryInterpreterTransactionManager;
13
14
  placeholderValues: Record<string, unknown>;
14
15
  onQuery?: (event: QueryEvent) => void;
16
+ tracingHelper: TracingHelper;
15
17
  };
16
18
  export declare class QueryInterpreter {
17
19
  #private;
18
- constructor({ transactionManager, placeholderValues, onQuery }: QueryInterpreterOptions);
20
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }: QueryInterpreterOptions);
19
21
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
20
22
  private interpretNode;
21
23
  }
@@ -0,0 +1,11 @@
1
+ import type { Context, Span, SpanOptions } from '@opentelemetry/api';
2
+ import type { Provider } from '@prisma/driver-adapter-utils';
3
+ export type SpanCallback<R> = (span?: Span, context?: Context) => R;
4
+ export type ExtendedSpanOptions = SpanOptions & {
5
+ name: string;
6
+ };
7
+ export interface TracingHelper {
8
+ runInChildSpan<R>(nameOrOptions: string | ExtendedSpanOptions, callback: SpanCallback<R>): R;
9
+ }
10
+ export declare const noopTracingHelper: TracingHelper;
11
+ export declare function providerToOtelSystem(provider: Provider): string;
@@ -1,13 +1,17 @@
1
1
  import { SqlDriverAdapter, Transaction } from '@prisma/driver-adapter-utils';
2
+ import { TracingHelper } from '../tracing';
2
3
  import { Options, TransactionInfo } from './Transaction';
3
4
  export declare class TransactionManager {
5
+ #private;
4
6
  private transactions;
5
7
  private closedTransactions;
6
8
  private readonly driverAdapter;
7
9
  private readonly transactionOptions;
8
- constructor({ driverAdapter, transactionOptions }: {
10
+ private readonly tracingHelper;
11
+ constructor({ driverAdapter, transactionOptions, tracingHelper, }: {
9
12
  driverAdapter: SqlDriverAdapter;
10
13
  transactionOptions: Options;
14
+ tracingHelper: TracingHelper;
11
15
  });
12
16
  startTransaction(options?: Options): Promise<TransactionInfo>;
13
17
  commitTransaction(transactionId: string): Promise<void>;
@@ -1,14 +1,8 @@
1
- import { Error as DriverAdapterError } from '@prisma/driver-adapter-utils';
2
1
  export declare class TransactionManagerError extends Error {
3
2
  meta?: Record<string, unknown> | undefined;
4
3
  code: string;
5
4
  constructor(message: string, meta?: Record<string, unknown> | undefined);
6
5
  }
7
- export declare class TransactionDriverAdapterError extends TransactionManagerError {
8
- constructor(message: string, errorParams: {
9
- driverAdapterError: DriverAdapterError;
10
- });
11
- }
12
6
  export declare class TransactionNotFoundError extends TransactionManagerError {
13
7
  constructor();
14
8
  }
@@ -18,7 +12,7 @@ export declare class TransactionClosedError extends TransactionManagerError {
18
12
  export declare class TransactionRolledBackError extends TransactionManagerError {
19
13
  constructor(operation: string);
20
14
  }
21
- export declare class TransactionStartTimoutError extends TransactionManagerError {
15
+ export declare class TransactionStartTimeoutError extends TransactionManagerError {
22
16
  constructor();
23
17
  }
24
18
  export declare class TransactionExecutionTimeoutError extends TransactionManagerError {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/client-engine-runtime",
3
- "version": "6.7.0-dev.1",
3
+ "version": "6.7.0-dev.10",
4
4
  "description": "This package is intended for Prisma's internal use",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -25,12 +25,13 @@
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
27
  "@bugsnag/cuid": "3.2.1",
28
+ "@opentelemetry/api": "1.9.0",
28
29
  "@paralleldrive/cuid2": "2.2.2",
29
30
  "nanoid": "5.1.5",
30
31
  "ulid": "3.0.0",
31
32
  "uuid": "11.1.0",
32
- "@prisma/debug": "6.7.0-dev.1",
33
- "@prisma/driver-adapter-utils": "6.7.0-dev.1"
33
+ "@prisma/debug": "6.7.0-dev.10",
34
+ "@prisma/driver-adapter-utils": "6.7.0-dev.10"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/jest": "29.5.14",