@prisma/client-engine-runtime 6.7.0-dev.3 → 6.7.0-dev.31

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
@@ -33,11 +33,277 @@ __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
+
213
+ // src/interpreter/DataMapper.ts
214
+ function applyDataMap(data, structure) {
215
+ switch (structure.type) {
216
+ case "Object":
217
+ return mapArrayOrObject(data, structure.fields);
218
+ case "Value":
219
+ return mapValue(data, structure.resultType);
220
+ default:
221
+ assertNever(structure, `Invalid data mapping type: '${structure.type}'`);
222
+ }
223
+ }
224
+ function mapArrayOrObject(data, fields) {
225
+ if (data === null) return null;
226
+ if (Array.isArray(data)) {
227
+ const rows = data;
228
+ return rows.map((row) => mapObject(row, fields));
229
+ }
230
+ if (typeof data === "object") {
231
+ const row = data;
232
+ return mapObject(row, fields);
233
+ }
234
+ throw new Error(`DataMapper: Expected an array or an object, got: ${typeof data}`);
235
+ }
236
+ function mapObject(data, fields) {
237
+ if (typeof data !== "object") {
238
+ throw new Error(`DataMapper: Expected an object, but got '${typeof data}'`);
239
+ }
240
+ const result = {};
241
+ for (const [name, node] of Object.entries(fields)) {
242
+ switch (node.type) {
243
+ case "Object":
244
+ if (Object.hasOwn(data, name)) {
245
+ result[name] = mapArrayOrObject(data[name], node.fields);
246
+ } else {
247
+ throw new Error(
248
+ `DataMapper: Missing data field (Object): '${name}'; node: ${JSON.stringify(node)}; data: ${JSON.stringify(data)}`
249
+ );
250
+ }
251
+ break;
252
+ case "Value":
253
+ {
254
+ const dbName = node.dbName;
255
+ if (Object.hasOwn(data, dbName)) {
256
+ result[name] = mapValue(data[dbName], node.resultType);
257
+ } else {
258
+ throw new Error(
259
+ `DataMapper: Missing data field (Value): '${dbName}'; node: ${JSON.stringify(node)}; data: ${JSON.stringify(data)}`
260
+ );
261
+ }
262
+ }
263
+ break;
264
+ default:
265
+ assertNever(node, `DataMapper: Invalid data mapping node type: '${node.type}'`);
266
+ }
267
+ }
268
+ return result;
269
+ }
270
+ function mapValue(value, resultType) {
271
+ if (value === null) return null;
272
+ switch (resultType.type) {
273
+ case "Any":
274
+ return value;
275
+ case "String":
276
+ return typeof value === "string" ? value : `${value}`;
277
+ case "Int":
278
+ return typeof value === "number" ? value : parseInt(`${value}`, 10);
279
+ case "BigInt":
280
+ return typeof value === "bigint" ? value : BigInt(`${value}`);
281
+ case "Float":
282
+ return typeof value === "number" ? value : parseFloat(`${value}`);
283
+ case "Boolean":
284
+ return typeof value === "boolean" ? value : value !== "0";
285
+ case "Decimal":
286
+ return typeof value === "number" ? value : parseFloat(`${value}`);
287
+ case "Date":
288
+ return value instanceof Date ? value : /* @__PURE__ */ new Date(`${value}`);
289
+ case "Array": {
290
+ const values = value;
291
+ return values.map((v) => {
292
+ mapValue(v, resultType.inner);
293
+ });
294
+ }
295
+ case "Object":
296
+ return typeof value === "object" ? value : { value };
297
+ case "Bytes":
298
+ if (typeof value !== "string") {
299
+ throw new Error(`DataMapper: Bytes data is not a string, got: ${typeof value}`);
300
+ }
301
+ return value;
302
+ default:
303
+ assertNever(resultType, `DataMapper: Unknown result type: ${resultType.type}`);
304
+ }
305
+ }
306
+
41
307
  // src/interpreter/generators.ts
42
308
  var import_cuid = __toESM(require("@bugsnag/cuid"));
43
309
  var import_cuid2 = require("@paralleldrive/cuid2");
@@ -52,6 +318,7 @@ var GeneratorRegistry = class {
52
318
  this.register("cuid", new CuidGenerator());
53
319
  this.register("ulid", new UlidGenerator());
54
320
  this.register("nanoid", new NanoIdGenerator());
321
+ this.register("product", new ProductGenerator());
55
322
  }
56
323
  /**
57
324
  * Returns a snapshot of the generator registry. It's 'frozen' in time at the moment of this
@@ -114,6 +381,22 @@ var NanoIdGenerator = class {
114
381
  }
115
382
  }
116
383
  };
384
+ var ProductGenerator = class {
385
+ generate(lhs, rhs) {
386
+ if (lhs === void 0 || rhs === void 0) {
387
+ throw new Error("Invalid Product generator arguments");
388
+ }
389
+ if (Array.isArray(lhs) && Array.isArray(rhs)) {
390
+ return lhs.flatMap((l) => rhs.map((r) => [l, r]));
391
+ } else if (Array.isArray(lhs)) {
392
+ return lhs.map((l) => [l, rhs]);
393
+ } else if (Array.isArray(rhs)) {
394
+ return rhs.map((r) => [lhs, r]);
395
+ } else {
396
+ return [[lhs, rhs]];
397
+ }
398
+ }
399
+ };
117
400
 
118
401
  // src/QueryPlan.ts
119
402
  function isPrismaValuePlaceholder(value) {
@@ -123,46 +406,49 @@ function isPrismaValueGenerator(value) {
123
406
  return typeof value === "object" && value !== null && value["prisma__type"] === "generatorCall";
124
407
  }
125
408
 
126
- // src/utils.ts
127
- function assertNever(_, message) {
128
- throw new Error(message);
129
- }
130
-
131
409
  // src/interpreter/renderQuery.ts
132
410
  function renderQuery(dbQuery, scope, generators) {
133
411
  const queryType = dbQuery.type;
134
412
  switch (queryType) {
135
413
  case "rawSql":
136
- return renderRawSql(dbQuery.sql, substituteParams(dbQuery.params, scope, generators));
414
+ return renderRawSql(dbQuery.sql, evaluateParams(dbQuery.params, scope, generators));
137
415
  case "templateSql":
138
416
  return renderTemplateSql(
139
417
  dbQuery.fragments,
140
418
  dbQuery.placeholderFormat,
141
- substituteParams(dbQuery.params, scope, generators)
419
+ evaluateParams(dbQuery.params, scope, generators)
142
420
  );
143
421
  default:
144
422
  assertNever(queryType, `Invalid query type`);
145
423
  }
146
424
  }
147
- function substituteParams(params, scope, generators) {
148
- return params.map((param) => {
149
- if (isPrismaValueGenerator(param)) {
150
- const { name, args } = param.prisma__value;
425
+ function evaluateParams(params, scope, generators) {
426
+ return params.map((param) => evaluateParam(param, scope, generators));
427
+ }
428
+ function evaluateParam(param, scope, generators) {
429
+ let value = param;
430
+ while (doesRequireEvaluation(value)) {
431
+ if (isPrismaValuePlaceholder(value)) {
432
+ const found = scope[value.prisma__value.name];
433
+ if (found === void 0) {
434
+ throw new Error(`Missing value for query variable ${value.prisma__value.name}`);
435
+ }
436
+ value = found;
437
+ } else if (isPrismaValueGenerator(value)) {
438
+ const { name, args } = value.prisma__value;
151
439
  const generator = generators[name];
152
440
  if (!generator) {
153
441
  throw new Error(`Encountered an unknown generator '${name}'`);
154
442
  }
155
- return generator.generate(...args);
156
- }
157
- if (!isPrismaValuePlaceholder(param)) {
158
- return param;
159
- }
160
- const value = scope[param.prisma__value.name];
161
- if (value === void 0) {
162
- throw new Error(`Missing value for query variable ${param.prisma__value.name}`);
443
+ value = generator.generate(...args.map((arg) => evaluateParam(arg, scope, generators)));
444
+ } else {
445
+ assertNever(value, `Unexpected unevaluated value type: ${value}`);
163
446
  }
164
- return value;
165
- });
447
+ }
448
+ if (Array.isArray(value)) {
449
+ value = value.map((el) => evaluateParam(el, scope, generators));
450
+ }
451
+ return value;
166
452
  }
167
453
  function renderTemplateSql(fragments, placeholderFormat, params) {
168
454
  let paramIndex = 0;
@@ -191,6 +477,29 @@ function renderTemplateSql(fragments, placeholderFormat, params) {
191
477
  }).join(",");
192
478
  return `(${placeholders})`;
193
479
  }
480
+ case "parameterTupleList": {
481
+ if (paramIndex >= params.length) {
482
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
483
+ }
484
+ const paramValue = params[paramIndex++];
485
+ if (!Array.isArray(paramValue)) {
486
+ throw new Error(`Malformed query template. Tuple list expected.`);
487
+ }
488
+ if (paramValue.length === 0) {
489
+ throw new Error(`Malformed query template. Tuple list cannot be empty.`);
490
+ }
491
+ const tupleList = paramValue.map((tuple) => {
492
+ if (!Array.isArray(tuple)) {
493
+ throw new Error(`Malformed query template. Tuple expected.`);
494
+ }
495
+ const elements = tuple.map((value) => {
496
+ flattenedParams.push(value);
497
+ return formatPlaceholder(placeholderFormat, placeholderNumber++);
498
+ }).join(",");
499
+ return `(${elements})`;
500
+ }).join(",");
501
+ return tupleList;
502
+ }
194
503
  default:
195
504
  assertNever(fragmentType, "Invalid fragment type");
196
505
  }
@@ -249,6 +558,9 @@ function placeholderTypeToArgType(type) {
249
558
  }
250
559
  return mappedType;
251
560
  }
561
+ function doesRequireEvaluation(param) {
562
+ return isPrismaValuePlaceholder(param) || isPrismaValueGenerator(param);
563
+ }
252
564
 
253
565
  // src/interpreter/serialize.ts
254
566
  function serialize(resultSet) {
@@ -278,13 +590,17 @@ var QueryInterpreter = class {
278
590
  #placeholderValues;
279
591
  #onQuery;
280
592
  #generators = new GeneratorRegistry();
281
- constructor({ transactionManager, placeholderValues, onQuery }) {
593
+ #tracingHelper;
594
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper }) {
282
595
  this.#transactionManager = transactionManager;
283
596
  this.#placeholderValues = placeholderValues;
284
597
  this.#onQuery = onQuery;
598
+ this.#tracingHelper = tracingHelper;
285
599
  }
286
600
  async run(queryPlan, queryable) {
287
- return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot());
601
+ return this.interpretNode(queryPlan, queryable, this.#placeholderValues, this.#generators.snapshot()).catch(
602
+ (e) => rethrowAsUserFacing(e)
603
+ );
288
604
  }
289
605
  async interpretNode(node, queryable, scope, generators) {
290
606
  switch (node.type) {
@@ -323,13 +639,13 @@ var QueryInterpreter = class {
323
639
  }
324
640
  case "execute": {
325
641
  const query = renderQuery(node.args, scope, generators);
326
- return this.#withQueryEvent(query, async () => {
642
+ return this.#withQueryEvent(query, queryable, async () => {
327
643
  return await queryable.executeRaw(query);
328
644
  });
329
645
  }
330
646
  case "query": {
331
647
  const query = renderQuery(node.args, scope, generators);
332
- return this.#withQueryEvent(query, async () => {
648
+ return this.#withQueryEvent(query, queryable, async () => {
333
649
  return serialize(await queryable.queryRaw(query));
334
650
  });
335
651
  }
@@ -390,24 +706,38 @@ var QueryInterpreter = class {
390
706
  throw e;
391
707
  }
392
708
  }
393
- default: {
394
- node;
395
- throw new Error(`Unexpected node type: ${node.type}`);
709
+ case "dataMap": {
710
+ const data = await this.interpretNode(node.args.expr, queryable, scope, generators);
711
+ return applyDataMap(data, node.args.structure);
396
712
  }
713
+ default:
714
+ assertNever(node, `Unexpected node type: ${node.type}`);
397
715
  }
398
716
  }
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;
717
+ #withQueryEvent(query, queryable, execute) {
718
+ return this.#tracingHelper.runInChildSpan(
719
+ {
720
+ name: "db_query",
721
+ kind: import_api.SpanKind.CLIENT,
722
+ attributes: {
723
+ "db.query.text": query.sql,
724
+ "db.system.name": providerToOtelSystem(queryable.provider)
725
+ }
726
+ },
727
+ async () => {
728
+ const timestamp = /* @__PURE__ */ new Date();
729
+ const startInstant = performance.now();
730
+ const result = await execute();
731
+ const endInstant = performance.now();
732
+ this.#onQuery?.({
733
+ timestamp,
734
+ duration: endInstant - startInstant,
735
+ query: query.sql,
736
+ params: query.args
737
+ });
738
+ return result;
739
+ }
740
+ );
411
741
  }
412
742
  };
413
743
  function isEmpty(value) {
@@ -452,6 +782,8 @@ function attachChildrenToParent(parentRecord, children) {
452
782
  function filterChildRecords(records, parentRecord, joinExpr) {
453
783
  if (Array.isArray(records)) {
454
784
  return records.filter((record) => childRecordMatchesParent(asRecord(record), parentRecord, joinExpr));
785
+ } else if (records === null) {
786
+ return null;
455
787
  } else {
456
788
  const record = asRecord(records);
457
789
  return childRecordMatchesParent(record, parentRecord, joinExpr) ? record : null;
@@ -540,11 +872,20 @@ var TransactionManager = class {
540
872
  closedTransactions = [];
541
873
  driverAdapter;
542
874
  transactionOptions;
543
- constructor({ driverAdapter, transactionOptions }) {
875
+ tracingHelper;
876
+ constructor({
877
+ driverAdapter,
878
+ transactionOptions,
879
+ tracingHelper
880
+ }) {
544
881
  this.driverAdapter = driverAdapter;
545
882
  this.transactionOptions = transactionOptions;
883
+ this.tracingHelper = tracingHelper;
546
884
  }
547
885
  async startTransaction(options) {
886
+ return await this.tracingHelper.runInChildSpan("start_transaction", () => this.#startTransactionImpl(options));
887
+ }
888
+ async #startTransactionImpl(options) {
548
889
  const validatedOptions = options !== void 0 ? this.validateOptions(options) : this.transactionOptions;
549
890
  const transaction = {
550
891
  id: await randomUUID(),
@@ -578,12 +919,16 @@ var TransactionManager = class {
578
919
  }
579
920
  }
580
921
  async commitTransaction(transactionId) {
581
- const txw = this.getActiveTransaction(transactionId, "commit");
582
- await this.closeTransaction(txw, "committed");
922
+ return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
923
+ const txw = this.getActiveTransaction(transactionId, "commit");
924
+ await this.closeTransaction(txw, "committed");
925
+ });
583
926
  }
584
927
  async rollbackTransaction(transactionId) {
585
- const txw = this.getActiveTransaction(transactionId, "rollback");
586
- await this.closeTransaction(txw, "rolled_back");
928
+ return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
929
+ const txw = this.getActiveTransaction(transactionId, "rollback");
930
+ await this.closeTransaction(txw, "rolled_back");
931
+ });
587
932
  }
588
933
  getTransaction(txInfo, operation) {
589
934
  const tx = this.getActiveTransaction(txInfo.id, operation);
@@ -673,6 +1018,8 @@ var TransactionManager = class {
673
1018
  QueryInterpreter,
674
1019
  TransactionManager,
675
1020
  TransactionManagerError,
1021
+ UserFacingError,
676
1022
  isPrismaValueGenerator,
677
- isPrismaValuePlaceholder
1023
+ isPrismaValuePlaceholder,
1024
+ noopTracingHelper
678
1025
  });