@prisma/client-engine-runtime 7.5.0-dev.5 → 7.5.0-dev.50

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
@@ -37,7 +37,7 @@ __export(index_exports, {
37
37
  UserFacingError: () => UserFacingError,
38
38
  applySqlCommenters: () => applySqlCommenters,
39
39
  convertCompactedRows: () => convertCompactedRows,
40
- deserializeJsonResponse: () => deserializeJsonResponse,
40
+ deserializeJsonObject: () => deserializeJsonObject,
41
41
  doKeysMatch: () => doKeysMatch,
42
42
  isDeepStrictEqual: () => isDeepStrictEqual,
43
43
  isPrismaValueGenerator: () => isPrismaValueGenerator,
@@ -170,7 +170,10 @@ function normalizeJsonProtocolValues(result) {
170
170
  function isTaggedValue(value) {
171
171
  return value !== null && typeof value == "object" && typeof value["$type"] === "string";
172
172
  }
173
- function normalizeTaggedValue({ $type, value }) {
173
+ function normalizeTaggedValue({
174
+ $type,
175
+ value
176
+ }) {
174
177
  switch ($type) {
175
178
  case "BigInt":
176
179
  return { $type, value: String(value) };
@@ -182,6 +185,12 @@ function normalizeTaggedValue({ $type, value }) {
182
185
  return { $type, value: String(new import_client_runtime_utils2.Decimal(value)) };
183
186
  case "Json":
184
187
  return { $type, value: JSON.stringify(JSON.parse(value)) };
188
+ case "Raw":
189
+ return { $type, value };
190
+ case "FieldRef":
191
+ return { $type, value };
192
+ case "Enum":
193
+ return { $type, value };
185
194
  default:
186
195
  assertNever(value, "Unknown tagged value");
187
196
  }
@@ -193,12 +202,12 @@ function mapObjectValues(object, mapper) {
193
202
  }
194
203
  return result;
195
204
  }
196
- function deserializeJsonResponse(result) {
205
+ function deserializeJsonObject(result) {
197
206
  if (result === null) {
198
207
  return result;
199
208
  }
200
209
  if (Array.isArray(result)) {
201
- return result.map(deserializeJsonResponse);
210
+ return result.map(deserializeJsonObject);
202
211
  }
203
212
  if (typeof result === "object") {
204
213
  if (isTaggedValue(result)) {
@@ -207,7 +216,7 @@ function deserializeJsonResponse(result) {
207
216
  if (result.constructor !== null && result.constructor.name !== "Object") {
208
217
  return result;
209
218
  }
210
- return mapObjectValues(result, deserializeJsonResponse);
219
+ return mapObjectValues(result, deserializeJsonObject);
211
220
  }
212
221
  return result;
213
222
  }
@@ -225,6 +234,12 @@ function deserializeTaggedValue({ $type, value }) {
225
234
  return new import_client_runtime_utils2.Decimal(value);
226
235
  case "Json":
227
236
  return JSON.parse(value);
237
+ case "Raw":
238
+ return value;
239
+ case "FieldRef":
240
+ throw new Error("FieldRef tagged values cannot be deserialized to JavaScript values");
241
+ case "Enum":
242
+ return value;
228
243
  default:
229
244
  assertNever(value, "Unknown tagged value");
230
245
  }
@@ -456,7 +471,7 @@ function resolveArgPlaceholders(args, placeholderValues) {
456
471
  function convertCompactedRows(rows, compiledBatch, placeholderValues = {}) {
457
472
  const keysPerRow = rows.map(
458
473
  (item) => compiledBatch.keys.reduce((acc, key) => {
459
- acc[key] = deserializeJsonResponse(item[key]);
474
+ acc[key] = deserializeJsonObject(item[key]);
460
475
  return acc;
461
476
  }, {})
462
477
  );
@@ -742,6 +757,9 @@ function normalizeDateTime(dt) {
742
757
  return dtWithTz;
743
758
  }
744
759
 
760
+ // src/interpreter/query-interpreter.ts
761
+ var import_klona2 = require("klona");
762
+
745
763
  // src/sql-commenter.ts
746
764
  var import_klona = require("klona");
747
765
  function formatSqlComment(tags) {
@@ -1025,8 +1043,11 @@ function paginateSingleList(list, { cursor, skip, take }) {
1025
1043
  const end = take !== null ? start + take : list.length;
1026
1044
  return list.slice(start, end);
1027
1045
  }
1028
- function getRecordKey(record, fields) {
1029
- return JSON.stringify(fields.map((field) => record[field]));
1046
+ function getRecordKey(record, fields, mappers) {
1047
+ const array = fields.map(
1048
+ (field, index) => mappers?.[index] ? record[field] !== null ? mappers[index](record[field]) : null : record[field]
1049
+ );
1050
+ return JSON.stringify(array);
1030
1051
  }
1031
1052
 
1032
1053
  // src/query-plan.ts
@@ -1063,7 +1084,11 @@ function evaluateArg(arg, scope, generators) {
1063
1084
  if (found === void 0) {
1064
1085
  throw new Error(`Missing value for query variable ${arg.prisma__value.name}`);
1065
1086
  }
1066
- arg = found;
1087
+ if (arg.prisma__value.type === "DateTime" && typeof found === "string") {
1088
+ arg = new Date(found);
1089
+ } else {
1090
+ arg = found;
1091
+ }
1067
1092
  } else if (isPrismaValueGenerator(arg)) {
1068
1093
  const { name, args } = arg.prisma__value;
1069
1094
  const generator = generators[name];
@@ -1121,7 +1146,10 @@ function renderFragment(fragment, placeholderFormat, ctx) {
1121
1146
  case "stringChunk":
1122
1147
  return fragment.chunk;
1123
1148
  case "parameterTuple": {
1124
- const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(",");
1149
+ const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => {
1150
+ const item = formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
1151
+ return `${fragment.itemPrefix}${item}${fragment.itemSuffix}`;
1152
+ }).join(fragment.itemSeparator);
1125
1153
  return `(${placeholders})`;
1126
1154
  }
1127
1155
  case "parameterTupleList": {
@@ -1480,7 +1508,7 @@ function doesSatisfyRule(data, rule) {
1480
1508
  }
1481
1509
  }
1482
1510
  function renderMessage(data, error) {
1483
- switch (error.error_identifier) {
1511
+ switch (error.errorIdentifier) {
1484
1512
  case "RELATION_VIOLATION":
1485
1513
  return `The change you are trying to make would violate the required relation '${error.context.relation}' between the \`${error.context.modelA}\` and \`${error.context.modelB}\` models.`;
1486
1514
  case "MISSING_RECORD":
@@ -1500,7 +1528,7 @@ function renderMessage(data, error) {
1500
1528
  }
1501
1529
  }
1502
1530
  function getErrorCode2(error) {
1503
- switch (error.error_identifier) {
1531
+ switch (error.errorIdentifier) {
1504
1532
  case "RELATION_VIOLATION":
1505
1533
  return "P2014";
1506
1534
  case "RECORDS_NOT_CONNECTED":
@@ -1615,7 +1643,7 @@ var QueryInterpreter = class _QueryInterpreter {
1615
1643
  sum += await this.#withQuerySpanAndEvent(
1616
1644
  commentedQuery,
1617
1645
  context.queryable,
1618
- () => context.queryable.executeRaw(commentedQuery).catch(
1646
+ () => context.queryable.executeRaw(cloneObject(commentedQuery)).catch(
1619
1647
  (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1620
1648
  )
1621
1649
  );
@@ -1630,7 +1658,7 @@ var QueryInterpreter = class _QueryInterpreter {
1630
1658
  const result = await this.#withQuerySpanAndEvent(
1631
1659
  commentedQuery,
1632
1660
  context.queryable,
1633
- () => context.queryable.queryRaw(commentedQuery).catch(
1661
+ () => context.queryable.queryRaw(cloneObject(commentedQuery)).catch(
1634
1662
  (err) => node.args.type === "rawSql" ? rethrowAsUserFacingRawError(err) : rethrowAsUserFacing(err)
1635
1663
  )
1636
1664
  );
@@ -1682,7 +1710,7 @@ var QueryInterpreter = class _QueryInterpreter {
1682
1710
  childRecords: (await this.interpretNode(joinExpr.child, context)).value
1683
1711
  }))
1684
1712
  );
1685
- return { value: attachChildrenToParents(parent, children), lastInsertId };
1713
+ return { value: attachChildrenToParents(parent, children, node.args.canAssumeStrictEquality), lastInsertId };
1686
1714
  }
1687
1715
  case "transaction": {
1688
1716
  if (!context.transactionManager.enabled) {
@@ -1729,8 +1757,9 @@ var QueryInterpreter = class _QueryInterpreter {
1729
1757
  }
1730
1758
  case "process": {
1731
1759
  const { value, lastInsertId } = await this.interpretNode(node.args.expr, context);
1732
- evaluateProcessingParameters(node.args.operations, context.scope, context.generators);
1733
- return { value: processRecords(value, node.args.operations), lastInsertId };
1760
+ const ops = cloneObject(node.args.operations);
1761
+ evaluateProcessingParameters(ops, context.scope, context.generators);
1762
+ return { value: processRecords(value, ops), lastInsertId };
1734
1763
  }
1735
1764
  case "initializeRecord": {
1736
1765
  const { lastInsertId } = await this.interpretNode(node.args.expr, context);
@@ -1823,12 +1852,13 @@ function mapField2(value, field) {
1823
1852
  }
1824
1853
  return value;
1825
1854
  }
1826
- function attachChildrenToParents(parentRecords, children) {
1855
+ function attachChildrenToParents(parentRecords, children, canAssumeStrictEquality) {
1827
1856
  for (const { joinExpr, childRecords } of children) {
1828
1857
  const parentKeys = joinExpr.on.map(([k]) => k);
1829
1858
  const childKeys = joinExpr.on.map(([, k]) => k);
1830
1859
  const parentMap = {};
1831
- for (const parent of Array.isArray(parentRecords) ? parentRecords : [parentRecords]) {
1860
+ const parentArray = Array.isArray(parentRecords) ? parentRecords : [parentRecords];
1861
+ for (const parent of parentArray) {
1832
1862
  const parentRecord = asRecord(parent);
1833
1863
  const key = getRecordKey(parentRecord, parentKeys);
1834
1864
  if (!parentMap[key]) {
@@ -1841,11 +1871,12 @@ function attachChildrenToParents(parentRecords, children) {
1841
1871
  parentRecord[joinExpr.parentField] = [];
1842
1872
  }
1843
1873
  }
1874
+ const mappers = canAssumeStrictEquality ? void 0 : inferKeyCasts(parentArray, parentKeys);
1844
1875
  for (const childRecord of Array.isArray(childRecords) ? childRecords : [childRecords]) {
1845
1876
  if (childRecord === null) {
1846
1877
  continue;
1847
1878
  }
1848
- const key = getRecordKey(asRecord(childRecord), childKeys);
1879
+ const key = getRecordKey(asRecord(childRecord), childKeys, mappers);
1849
1880
  for (const parentRecord of parentMap[key] ?? []) {
1850
1881
  if (joinExpr.isRelationUnique) {
1851
1882
  parentRecord[joinExpr.parentField] = childRecord;
@@ -1857,6 +1888,40 @@ function attachChildrenToParents(parentRecords, children) {
1857
1888
  }
1858
1889
  return parentRecords;
1859
1890
  }
1891
+ function inferKeyCasts(rows, keys) {
1892
+ function getKeyCast(type) {
1893
+ switch (type) {
1894
+ case "number":
1895
+ return Number;
1896
+ case "string":
1897
+ return String;
1898
+ case "boolean":
1899
+ return Boolean;
1900
+ case "bigint":
1901
+ return BigInt;
1902
+ default:
1903
+ return;
1904
+ }
1905
+ }
1906
+ const keyCasts = Array.from({ length: keys.length });
1907
+ let keysFound = 0;
1908
+ for (const parent of rows) {
1909
+ const parentRecord = asRecord(parent);
1910
+ for (const [i, key] of keys.entries()) {
1911
+ if (parentRecord[key] !== null && keyCasts[i] === void 0) {
1912
+ const keyCast = getKeyCast(typeof parentRecord[key]);
1913
+ if (keyCast !== void 0) {
1914
+ keyCasts[i] = keyCast;
1915
+ }
1916
+ keysFound++;
1917
+ }
1918
+ }
1919
+ if (keysFound === keys.length) {
1920
+ break;
1921
+ }
1922
+ }
1923
+ return keyCasts;
1924
+ }
1860
1925
  function evalFieldInitializer(initializer, lastInsertId, scope, generators) {
1861
1926
  switch (initializer.type) {
1862
1927
  case "value":
@@ -1916,6 +1981,9 @@ function evaluateProcessingParameters(ops, scope, generators) {
1916
1981
  evaluateProcessingParameters(nested, scope, generators);
1917
1982
  }
1918
1983
  }
1984
+ function cloneObject(value) {
1985
+ return (0, import_klona2.klona)(value);
1986
+ }
1919
1987
 
1920
1988
  // src/raw-json-protocol.ts
1921
1989
  var import_client_runtime_utils4 = require("@prisma/client-runtime-utils");
@@ -2072,13 +2140,42 @@ var TransactionManager = class {
2072
2140
  );
2073
2141
  }
2074
2142
  async #startTransactionImpl(options) {
2143
+ if (options.newTxId) {
2144
+ return await this.#withActiveTransactionLock(options.newTxId, "start", async (existing) => {
2145
+ if (existing.status !== "running") {
2146
+ throw new TransactionInternalConsistencyError(
2147
+ `Transaction in invalid state ${existing.status} when starting a nested transaction.`
2148
+ );
2149
+ }
2150
+ if (!existing.transaction) {
2151
+ throw new TransactionInternalConsistencyError(
2152
+ `Transaction missing underlying driver transaction when starting a nested transaction.`
2153
+ );
2154
+ }
2155
+ existing.depth += 1;
2156
+ const savepointName = this.#nextSavepointName(existing);
2157
+ existing.savepoints.push(savepointName);
2158
+ try {
2159
+ await this.#requiredCreateSavepoint(existing.transaction)(savepointName);
2160
+ } catch (e) {
2161
+ existing.depth -= 1;
2162
+ existing.savepoints.pop();
2163
+ throw e;
2164
+ }
2165
+ return { id: existing.id };
2166
+ });
2167
+ }
2075
2168
  const transaction = {
2076
2169
  id: await randomUUID(),
2077
2170
  status: "waiting",
2078
2171
  timer: void 0,
2079
2172
  timeout: options.timeout,
2080
2173
  startedAt: Date.now(),
2081
- transaction: void 0
2174
+ transaction: void 0,
2175
+ operationQueue: Promise.resolve(),
2176
+ depth: 1,
2177
+ savepoints: [],
2178
+ savepointCounter: 0
2082
2179
  };
2083
2180
  const abortController = new AbortController();
2084
2181
  const startTimer = createTimeoutIfDefined(() => abortController.abort(), options.maxWait);
@@ -2112,14 +2209,49 @@ var TransactionManager = class {
2112
2209
  }
2113
2210
  async commitTransaction(transactionId) {
2114
2211
  return await this.tracingHelper.runInChildSpan("commit_transaction", async () => {
2115
- const txw = this.#getActiveOrClosingTransaction(transactionId, "commit");
2116
- await this.#closeTransaction(txw, "committed");
2212
+ await this.#withActiveTransactionLock(transactionId, "commit", async (txw) => {
2213
+ if (txw.depth > 1) {
2214
+ if (!txw.transaction) throw new TransactionNotFoundError();
2215
+ const savepointName = txw.savepoints.at(-1);
2216
+ if (!savepointName) {
2217
+ throw new TransactionInternalConsistencyError(
2218
+ `Missing savepoint for nested commit. Depth: ${txw.depth}, transactionId: ${txw.id}`
2219
+ );
2220
+ }
2221
+ try {
2222
+ await this.#releaseSavepoint(txw.transaction, savepointName);
2223
+ } finally {
2224
+ txw.savepoints.pop();
2225
+ txw.depth -= 1;
2226
+ }
2227
+ return;
2228
+ }
2229
+ await this.#closeTransaction(txw, "committed");
2230
+ });
2117
2231
  });
2118
2232
  }
2119
2233
  async rollbackTransaction(transactionId) {
2120
2234
  return await this.tracingHelper.runInChildSpan("rollback_transaction", async () => {
2121
- const txw = this.#getActiveOrClosingTransaction(transactionId, "rollback");
2122
- await this.#closeTransaction(txw, "rolled_back");
2235
+ await this.#withActiveTransactionLock(transactionId, "rollback", async (txw) => {
2236
+ if (txw.depth > 1) {
2237
+ if (!txw.transaction) throw new TransactionNotFoundError();
2238
+ const savepointName = txw.savepoints.at(-1);
2239
+ if (!savepointName) {
2240
+ throw new TransactionInternalConsistencyError(
2241
+ `Missing savepoint for nested rollback. Depth: ${txw.depth}, transactionId: ${txw.id}`
2242
+ );
2243
+ }
2244
+ try {
2245
+ await this.#requiredRollbackToSavepoint(txw.transaction)(savepointName);
2246
+ await this.#releaseSavepoint(txw.transaction, savepointName);
2247
+ } finally {
2248
+ txw.savepoints.pop();
2249
+ txw.depth -= 1;
2250
+ }
2251
+ return;
2252
+ }
2253
+ await this.#closeTransaction(txw, "rolled_back");
2254
+ });
2123
2255
  });
2124
2256
  }
2125
2257
  async getTransaction(txInfo, operation) {
@@ -2163,22 +2295,90 @@ var TransactionManager = class {
2163
2295
  return transaction;
2164
2296
  }
2165
2297
  async cancelAllTransactions() {
2166
- await Promise.allSettled([...this.transactions.values()].map((tx) => this.#closeTransaction(tx, "rolled_back")));
2298
+ await Promise.allSettled(
2299
+ [...this.transactions.values()].map(
2300
+ (tx) => this.#runSerialized(tx, async () => {
2301
+ const current = this.transactions.get(tx.id);
2302
+ if (current) {
2303
+ await this.#closeTransaction(current, "rolled_back");
2304
+ }
2305
+ })
2306
+ )
2307
+ );
2308
+ }
2309
+ #nextSavepointName(transaction) {
2310
+ return `prisma_sp_${transaction.savepointCounter++}`;
2311
+ }
2312
+ #requiredCreateSavepoint(transaction) {
2313
+ if (transaction.createSavepoint) {
2314
+ return transaction.createSavepoint.bind(transaction);
2315
+ }
2316
+ throw new TransactionManagerError(
2317
+ `Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): createSavepoint is not implemented.`
2318
+ );
2319
+ }
2320
+ #requiredRollbackToSavepoint(transaction) {
2321
+ if (transaction.rollbackToSavepoint) {
2322
+ return transaction.rollbackToSavepoint.bind(transaction);
2323
+ }
2324
+ throw new TransactionManagerError(
2325
+ `Nested transactions are not supported by adapter "${transaction.adapterName}" (${transaction.provider}): rollbackToSavepoint is not implemented.`
2326
+ );
2327
+ }
2328
+ async #releaseSavepoint(transaction, name) {
2329
+ if (transaction.releaseSavepoint) {
2330
+ await transaction.releaseSavepoint(name);
2331
+ }
2332
+ }
2333
+ #debugTransactionAlreadyClosedOnTimeout(transactionId) {
2334
+ debug("Transaction already committed or rolled back when timeout happened.", transactionId);
2167
2335
  }
2168
2336
  #startTransactionTimeout(transactionId, timeout) {
2169
2337
  const timeoutStartedAt = Date.now();
2170
2338
  const timer = createTimeoutIfDefined(async () => {
2171
2339
  debug("Transaction timed out.", { transactionId, timeoutStartedAt, timeout });
2172
2340
  const tx = this.transactions.get(transactionId);
2173
- if (tx && ["running", "waiting"].includes(tx.status)) {
2174
- await this.#closeTransaction(tx, "timed_out");
2175
- } else {
2176
- debug("Transaction already committed or rolled back when timeout happened.", transactionId);
2177
- }
2341
+ if (!tx) {
2342
+ this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
2343
+ return;
2344
+ }
2345
+ await this.#runSerialized(tx, async () => {
2346
+ const current = this.transactions.get(transactionId);
2347
+ if (current && ["running", "waiting"].includes(current.status)) {
2348
+ await this.#closeTransaction(current, "timed_out");
2349
+ } else {
2350
+ this.#debugTransactionAlreadyClosedOnTimeout(transactionId);
2351
+ }
2352
+ });
2178
2353
  }, timeout);
2179
2354
  timer?.unref?.();
2180
2355
  return timer;
2181
2356
  }
2357
+ // Any operation that mutates or closes a transaction must run through this lock so
2358
+ // status/savepoint/depth checks and updates happen against a stable view of state.
2359
+ async #withActiveTransactionLock(transactionId, operation, callback) {
2360
+ const tx = this.#getActiveOrClosingTransaction(transactionId, operation);
2361
+ return await this.#runSerialized(tx, async () => {
2362
+ const current = this.#getActiveOrClosingTransaction(transactionId, operation);
2363
+ return await callback(current);
2364
+ });
2365
+ }
2366
+ // Serializes operations per transaction id to prevent interleaving across awaits.
2367
+ // This avoids races where one operation mutates savepoint/depth state while another
2368
+ // operation is suspended, which could otherwise corrupt cleanup logic.
2369
+ async #runSerialized(tx, callback) {
2370
+ const previousOperation = tx.operationQueue;
2371
+ let releaseOperationLock;
2372
+ tx.operationQueue = new Promise((resolve) => {
2373
+ releaseOperationLock = resolve;
2374
+ });
2375
+ await previousOperation;
2376
+ try {
2377
+ return await callback();
2378
+ } finally {
2379
+ releaseOperationLock();
2380
+ }
2381
+ }
2182
2382
  async #closeTransaction(tx, status) {
2183
2383
  const createClosingPromise = async () => {
2184
2384
  debug("Closing transaction.", { transactionId: tx.id, status });
@@ -2266,7 +2466,7 @@ function createTimeoutIfDefined(cb, ms) {
2266
2466
  UserFacingError,
2267
2467
  applySqlCommenters,
2268
2468
  convertCompactedRows,
2269
- deserializeJsonResponse,
2469
+ deserializeJsonObject,
2270
2470
  doKeysMatch,
2271
2471
  isDeepStrictEqual,
2272
2472
  isPrismaValueGenerator,