@typed-sql/mysql 1.0.0-rc.0 → 2.0.0-rc.1

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +45 -56
  3. package/dist/packages/mysql/src/bulk.d.ts +22 -0
  4. package/dist/packages/mysql/src/bulk.d.ts.map +1 -0
  5. package/dist/packages/mysql/src/bulk.js +205 -0
  6. package/dist/packages/mysql/src/bulk.js.map +1 -0
  7. package/dist/packages/mysql/src/decoding.d.ts +26 -0
  8. package/dist/packages/mysql/src/decoding.d.ts.map +1 -0
  9. package/dist/packages/mysql/src/decoding.js +172 -0
  10. package/dist/packages/mysql/src/decoding.js.map +1 -0
  11. package/dist/packages/mysql/src/index.d.ts +5 -1
  12. package/dist/packages/mysql/src/index.d.ts.map +1 -1
  13. package/dist/packages/mysql/src/index.js +16 -3
  14. package/dist/packages/mysql/src/index.js.map +1 -1
  15. package/dist/packages/mysql/src/mysql2.d.ts +60 -1
  16. package/dist/packages/mysql/src/mysql2.d.ts.map +1 -1
  17. package/dist/packages/mysql/src/mysql2.js +402 -1
  18. package/dist/packages/mysql/src/mysql2.js.map +1 -1
  19. package/dist/packages/mysql/src/prepared.d.ts +20 -0
  20. package/dist/packages/mysql/src/prepared.d.ts.map +1 -0
  21. package/dist/packages/mysql/src/prepared.js +47 -0
  22. package/dist/packages/mysql/src/prepared.js.map +1 -0
  23. package/dist/packages/mysql/src/provider.d.ts.map +1 -1
  24. package/dist/packages/mysql/src/provider.js +8 -1
  25. package/dist/packages/mysql/src/provider.js.map +1 -1
  26. package/dist/packages/mysql/src/resolver.d.ts.map +1 -1
  27. package/dist/packages/mysql/src/resolver.js +20 -1
  28. package/dist/packages/mysql/src/resolver.js.map +1 -1
  29. package/dist/packages/mysql/src/routing.d.ts +22 -0
  30. package/dist/packages/mysql/src/routing.d.ts.map +1 -0
  31. package/dist/packages/mysql/src/routing.js +72 -0
  32. package/dist/packages/mysql/src/routing.js.map +1 -0
  33. package/dist/packages/mysql/src/runtime.d.ts +22 -8
  34. package/dist/packages/mysql/src/runtime.d.ts.map +1 -1
  35. package/dist/packages/mysql/src/runtime.js +530 -70
  36. package/dist/packages/mysql/src/runtime.js.map +1 -1
  37. package/dist/packages/mysql/src/semantics.d.ts +5 -0
  38. package/dist/packages/mysql/src/semantics.d.ts.map +1 -0
  39. package/dist/packages/mysql/src/semantics.js +203 -0
  40. package/dist/packages/mysql/src/semantics.js.map +1 -0
  41. package/dist/packages/mysql/src/stream.d.ts +32 -0
  42. package/dist/packages/mysql/src/stream.d.ts.map +1 -0
  43. package/dist/packages/mysql/src/stream.js +175 -0
  44. package/dist/packages/mysql/src/stream.js.map +1 -0
  45. package/dist/packages/mysql/src/version.d.ts +2 -0
  46. package/dist/packages/mysql/src/version.d.ts.map +1 -0
  47. package/dist/packages/mysql/src/version.js +2 -0
  48. package/dist/packages/mysql/src/version.js.map +1 -0
  49. package/package.json +4 -4
@@ -1,118 +1,578 @@
1
1
  var _a;
2
- import { renderQuery } from "@typed-sql/core";
2
+ import { createHash } from "node:crypto";
3
+ import { adapterCapabilities, assertExecutionCapabilities, createAdapterCapabilityResolver, databaseErrorCompletion, hasQueryResultValidator, observeQueryStream, QueryCardinalityError, queryResultValidationSource, renderQuery, runControlledExecution, startDatabaseObservation, UnsupportedExecutionCapabilityError, validateQueryResultRows, validateQueryResultStream, } from "@typed-sql/core";
4
+ import { createMySqlBulkCapability, mysqlBulk } from "./bulk.js";
5
+ import { decodeMySqlRows, encodeMySqlValue, MySqlDecoderPlanCache, } from "./decoding.js";
6
+ import { createMySqlPreparedQueryState, prepareMySqlQuery, } from "./prepared.js";
7
+ import { createMySqlQueryStream, validateMySqlStreamBatchSize } from "./stream.js";
3
8
  import { defaultMySqlTypePolicy } from "./type-policy.js";
4
- const defaultRuntimePolicy = defaultMySqlTypePolicy;
5
- const mysqlTypes = { tiny: 1, longlong: 8, date: 10, datetime: 12, timestamp: 7, json: 245, decimal: 246 };
9
+ import { MYSQL_DIALECT_VERSION } from "./version.js";
10
+ const defaultRuntimeTypePolicy = defaultMySqlTypePolicy;
11
+ const emptyBatchResults = Object.freeze([]);
12
+ function createMySqlObservationState(observer) {
13
+ return { observer, fingerprints: observer === undefined ? undefined : new WeakMap() };
14
+ }
15
+ function mysqlQueryFingerprint(state, query) {
16
+ const key = query;
17
+ const cached = state.fingerprints?.get(key);
18
+ if (cached !== undefined)
19
+ return cached;
20
+ const text = renderQuery(query, mysqlRenderer).text;
21
+ const fingerprint = `sha256:${createHash("sha256").update(`mysql\0${MYSQL_DIALECT_VERSION}\0${text}`).digest("hex")}`;
22
+ state.fingerprints?.set(key, fingerprint);
23
+ return fingerprint;
24
+ }
6
25
  export const mysqlRenderer = Object.freeze({
7
26
  placeholder: () => "?",
8
27
  quoteIdentifier: (identifier) => `\`${identifier.replaceAll("`", "``")}\``,
9
28
  });
10
- function encoded(value) {
11
- return typeof value === "bigint" ? value.toString() : Array.isArray(value) ? value.map(encoded) : value;
12
- }
13
- function finite(value, label) {
14
- const result = Number(value);
15
- if (!Number.isFinite(result))
16
- throw new RangeError(`${label} value ${value} cannot be represented as a finite number`);
17
- return result;
18
- }
19
- function decode(value, field, policy, decimal) {
20
- if (value === null || value === undefined)
21
- return value;
22
- if (field.columnType === mysqlTypes.longlong) {
23
- if (policy.bigint === "bigint")
24
- return BigInt(String(value));
25
- if (policy.bigint === "number") {
26
- const result = finite(String(value), "bigint");
27
- if (!Number.isSafeInteger(result))
28
- throw new RangeError(`bigint value ${value} exceeds JavaScript's safe integer range`);
29
- return result;
30
- }
31
- return String(value);
32
- }
33
- if (field.columnType === mysqlTypes.decimal) {
34
- if (policy.decimal === "number")
35
- return finite(String(value), "decimal");
36
- if (policy.decimal === "Decimal") {
37
- if (decimal === undefined)
38
- throw new TypeError("decimal=Decimal requires a decimal(value) codec");
39
- return decimal(String(value));
40
- }
41
- return String(value);
42
- }
43
- if ([mysqlTypes.date, mysqlTypes.datetime, mysqlTypes.timestamp].includes(field.columnType)) {
44
- return policy.date === "string" ? String(value) : value instanceof Date ? value : new Date(String(value));
45
- }
46
- if (field.columnType === mysqlTypes.json && policy.json === "string")
47
- return typeof value === "string" ? value : JSON.stringify(value);
48
- if (field.columnType === mysqlTypes.tiny && field.columnLength === 1 && policy.tinyint1 === "boolean")
49
- return value === true || value === 1 || value === "1";
50
- return value;
29
+ function createMySqlConnectionOperation() {
30
+ let finish;
31
+ const completion = new Promise((resolve) => {
32
+ finish = resolve;
33
+ });
34
+ return { completion, finish };
51
35
  }
52
36
  class MySqlDatabaseImplementation {
53
37
  #pool;
54
38
  #connection;
55
39
  #ownsPool;
56
- #policy;
57
- #decimal;
58
40
  #depth;
59
- constructor(pool, connection, ownsPool, policy, decimal, depth) {
41
+ #prepared;
42
+ #observation;
43
+ #decoderPlans;
44
+ #executes;
45
+ #streams;
46
+ #transactionState;
47
+ #scopeOpen = true;
48
+ executionCapabilities;
49
+ [adapterCapabilities];
50
+ constructor(pool, connection, ownsPool, depth, prepared, decoderPlans, observation, transactionState) {
60
51
  this.#pool = pool;
61
52
  this.#connection = connection;
62
53
  this.#ownsPool = ownsPool;
63
- this.#policy = policy;
64
- this.#decimal = decimal;
65
54
  this.#depth = depth;
55
+ this.#prepared = prepared;
56
+ this.#decoderPlans = decoderPlans;
57
+ this.#observation = observation;
58
+ this.#executes = connection === undefined ? undefined : new Set();
59
+ this.#streams = connection === undefined ? undefined : new Set();
60
+ this.#transactionState = transactionState;
61
+ this.executionCapabilities = Object.freeze({
62
+ cancellation: pool.executionCapabilities?.cancellation ?? false,
63
+ deadlines: pool.executionCapabilities?.deadlines ?? false,
64
+ });
65
+ const bulkTransport = {
66
+ loadData: (statement, chunks, options) => this.#loadData(statement, chunks, options),
67
+ };
68
+ this[adapterCapabilities] = createAdapterCapabilityResolver(pool.bulkLoad === true ? [[mysqlBulk, createMySqlBulkCapability(bulkTransport)]] : []);
66
69
  }
67
70
  async execute(query) {
68
- const rendered = renderQuery(query, mysqlRenderer);
69
- const result = await (this.#connection ?? this.#pool).execute(rendered.text, rendered.values.map(encoded));
71
+ if (this.#observation.observer === undefined) {
72
+ return this.#validateRows(query, await this.#executeUnobserved(query));
73
+ }
74
+ return this.#observeQuery(query, "many", async () => this.#validateRows(query, await this.#executeUnobserved(query)));
75
+ }
76
+ async #executeUnobserved(query) {
77
+ this.#assertScopeOpen();
78
+ this.#assertConnectionAvailable("execute a query");
79
+ if (this.#connection === undefined)
80
+ return this.#executeOn(this.#pool, query);
81
+ const operation = createMySqlConnectionOperation();
82
+ this.#executes.add(operation);
83
+ this.#transactionState.execute = operation;
84
+ try {
85
+ return await this.#executeOn(this.#connection, query);
86
+ }
87
+ finally {
88
+ this.#executes.delete(operation);
89
+ if (this.#transactionState?.execute === operation)
90
+ this.#transactionState.execute = undefined;
91
+ operation.finish();
92
+ }
93
+ }
94
+ async all(query, options) {
95
+ if (this.#observation.observer === undefined) {
96
+ return this.#validateRows(query, await this.#allUnobserved(query, options));
97
+ }
98
+ return this.#observeQuery(query, "many", async () => this.#validateRows(query, await this.#allUnobserved(query, options)));
99
+ }
100
+ async #allUnobserved(query, options) {
101
+ if (options === undefined || (options.signal === undefined && options.deadline === undefined))
102
+ return this.#executeUnobserved(query);
103
+ assertExecutionCapabilities(this.executionCapabilities, options);
104
+ this.#assertScopeOpen();
105
+ this.#assertConnectionAvailable("execute a query");
106
+ if (options.signal?.aborted || (options.deadline !== undefined && Number(options.deadline) <= Date.now())) {
107
+ return runControlledExecution(options, async () => [], () => undefined);
108
+ }
109
+ if (this.#connection === undefined) {
110
+ const connection = await this.#pool.getConnection();
111
+ if (connection.destroy === undefined) {
112
+ connection.release();
113
+ throw new UnsupportedExecutionCapabilityError(options.signal === undefined ? "deadlines" : "cancellation");
114
+ }
115
+ let discarded = false;
116
+ try {
117
+ return await this.#executeControlledOn(connection, query, options, () => {
118
+ connection.destroy();
119
+ discarded = true;
120
+ });
121
+ }
122
+ finally {
123
+ if (!discarded)
124
+ connection.release();
125
+ }
126
+ }
127
+ const connection = this.#connection;
128
+ if (connection.destroy === undefined) {
129
+ throw new UnsupportedExecutionCapabilityError(options.signal === undefined ? "deadlines" : "cancellation");
130
+ }
131
+ const operationState = createMySqlConnectionOperation();
132
+ this.#executes.add(operationState);
133
+ this.#transactionState.execute = operationState;
134
+ try {
135
+ return await this.#executeControlledOn(connection, query, options, () => {
136
+ this.#transactionState.usable = false;
137
+ connection.destroy();
138
+ this.#transactionState.discarded = true;
139
+ });
140
+ }
141
+ finally {
142
+ this.#executes.delete(operationState);
143
+ if (this.#transactionState?.execute === operationState)
144
+ this.#transactionState.execute = undefined;
145
+ operationState.finish();
146
+ }
147
+ }
148
+ async #executeControlledOn(connection, query, options, cancel) {
149
+ return runControlledExecution(options, () => this.#executeOn(connection, query), cancel);
150
+ }
151
+ async one(query, options) {
152
+ if (this.#observation.observer === undefined) {
153
+ const rows = await this.#validateRows(query, await this.#allUnobserved(query, options));
154
+ if (rows.length !== 1)
155
+ throw new QueryCardinalityError("one", rows.length);
156
+ return rows[0];
157
+ }
158
+ const rows = await this.#observeQuery(query, "one", async () => {
159
+ const result = await this.#validateRows(query, await this.#allUnobserved(query, options));
160
+ if (result.length !== 1)
161
+ throw new QueryCardinalityError("one", result.length);
162
+ return result;
163
+ });
164
+ return rows[0];
165
+ }
166
+ async maybeOne(query, options) {
167
+ if (this.#observation.observer === undefined) {
168
+ const rows = await this.#validateRows(query, await this.#allUnobserved(query, options));
169
+ if (rows.length > 1)
170
+ throw new QueryCardinalityError("maybeOne", rows.length);
171
+ return rows[0];
172
+ }
173
+ const rows = await this.#observeQuery(query, "maybeOne", async () => {
174
+ const result = await this.#validateRows(query, await this.#allUnobserved(query, options));
175
+ if (result.length > 1)
176
+ throw new QueryCardinalityError("maybeOne", result.length);
177
+ return result;
178
+ });
179
+ return rows[0];
180
+ }
181
+ async #observeQuery(query, cardinality, operation) {
182
+ if (this.#observation.observer === undefined)
183
+ return operation();
184
+ const observation = startDatabaseObservation(this.#observation.observer, {
185
+ kind: "query",
186
+ dialect: "mysql",
187
+ grammarVersion: MYSQL_DIALECT_VERSION,
188
+ transactionDepth: this.#depth,
189
+ fingerprint: mysqlQueryFingerprint(this.#observation, query),
190
+ cardinality,
191
+ prepared: this.#prepared.queries.has(queryResultValidationSource(query)),
192
+ });
193
+ if (observation === undefined)
194
+ return operation();
195
+ try {
196
+ const rows = await observation.run(operation);
197
+ observation.end({ status: "success", rowCount: rows.length });
198
+ return rows;
199
+ }
200
+ catch (error) {
201
+ observation.end(databaseErrorCompletion(error), error);
202
+ throw error;
203
+ }
204
+ }
205
+ async #executeOn(executor, query) {
206
+ const prepared = this.#prepared.queries.get(queryResultValidationSource(query));
207
+ const rendered = prepared?.rendered ?? renderQuery(query, mysqlRenderer);
208
+ const result = await executor.execute(rendered.text, rendered.values.map(encodeMySqlValue));
70
209
  if (!Array.isArray(result.rows))
71
210
  return [];
72
- const fields = result.fields ?? [];
73
- return result.rows.map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => {
74
- const field = fields.find((candidate) => candidate.name === key);
75
- return [key, field === undefined ? value : decode(value, field, this.#policy, this.#decimal)];
76
- })));
211
+ const decoders = this.#decoderPlans.get(result.fields ?? []);
212
+ return decodeMySqlRows(result.rows, decoders);
213
+ }
214
+ async batch(queries) {
215
+ if (queries.length === 0)
216
+ return emptyBatchResults;
217
+ if (this.#observation.observer === undefined) {
218
+ return this.#validateBatch(queries, await this.#batchUnobserved(queries));
219
+ }
220
+ return this.#observeBatch(queries, async () => this.#validateBatch(queries, await this.#batchUnobserved(queries)));
221
+ }
222
+ async #batchUnobserved(queries) {
223
+ this.#assertScopeOpen();
224
+ this.#assertConnectionAvailable("execute a batch");
225
+ if (queries.length === 0)
226
+ return emptyBatchResults;
227
+ const orderedQueries = [...queries];
228
+ if (this.#connection !== undefined) {
229
+ const operation = createMySqlConnectionOperation();
230
+ this.#transactionState.batch = operation;
231
+ try {
232
+ return await this.#executeBatchOn(this.#connection, orderedQueries, () => this.#assertBatchCanContinue(operation));
233
+ }
234
+ finally {
235
+ if (this.#transactionState?.batch === operation)
236
+ this.#transactionState.batch = undefined;
237
+ operation.finish();
238
+ }
239
+ }
240
+ const connection = await this.#pool.getConnection();
241
+ let results;
242
+ try {
243
+ results = await this.#executeBatchOn(connection, orderedQueries);
244
+ }
245
+ catch (error) {
246
+ try {
247
+ connection.release();
248
+ }
249
+ catch {
250
+ /* Preserve the batch execution failure. */
251
+ }
252
+ throw error;
253
+ }
254
+ connection.release();
255
+ return results;
256
+ }
257
+ async #observeBatch(queries, operation) {
258
+ if (this.#observation.observer === undefined)
259
+ return operation();
260
+ const fingerprints = Object.freeze([...queries].map((query) => mysqlQueryFingerprint(this.#observation, query)));
261
+ const observation = startDatabaseObservation(this.#observation.observer, {
262
+ kind: "batch",
263
+ dialect: "mysql",
264
+ grammarVersion: MYSQL_DIALECT_VERSION,
265
+ transactionDepth: this.#depth,
266
+ fingerprints,
267
+ size: queries.length,
268
+ });
269
+ if (observation === undefined)
270
+ return operation();
271
+ try {
272
+ const results = await observation.run(operation);
273
+ observation.end({ status: "success" });
274
+ return results;
275
+ }
276
+ catch (error) {
277
+ observation.end(databaseErrorCompletion(error), error);
278
+ throw error;
279
+ }
280
+ }
281
+ async #executeBatchOn(connection, queries, beforeEach) {
282
+ const results = [];
283
+ for (const query of queries) {
284
+ beforeEach?.();
285
+ results.push(await this.#executeOn(connection, query));
286
+ }
287
+ return results;
288
+ }
289
+ stream(query, options = {}) {
290
+ this.#assertScopeOpen();
291
+ const prepared = this.#prepared.queries.get(queryResultValidationSource(query));
292
+ const rendered = prepared?.rendered ?? renderQuery(query, mysqlRenderer);
293
+ const batchSize = validateMySqlStreamBatchSize(options.batchSize);
294
+ let exposedStream;
295
+ const queryStream = createMySqlQueryStream({
296
+ openConnection: async () => {
297
+ if (this.#connection !== undefined) {
298
+ this.#assertScopeOpen();
299
+ this.#assertConnectionAvailable("start another query stream");
300
+ this.#transactionState.active = exposedStream;
301
+ return { connection: this.#connection, release: false };
302
+ }
303
+ return { connection: await this.#pool.getConnection(), release: true };
304
+ },
305
+ text: rendered.text,
306
+ values: rendered.values.map(encodeMySqlValue),
307
+ batchSize,
308
+ decoderPlans: this.#decoderPlans,
309
+ onClose: () => {
310
+ this.#streams?.delete(exposedStream);
311
+ if (this.#transactionState?.active === exposedStream)
312
+ this.#transactionState.active = undefined;
313
+ },
314
+ });
315
+ const validatedStream = hasQueryResultValidator(query)
316
+ ? validateQueryResultStream(query, queryStream, mysqlQueryFingerprint(this.#observation, query))
317
+ : queryStream;
318
+ exposedStream =
319
+ this.#observation.observer === undefined
320
+ ? validatedStream
321
+ : observeQueryStream(validatedStream, this.#observation.observer, {
322
+ kind: "stream",
323
+ dialect: "mysql",
324
+ grammarVersion: MYSQL_DIALECT_VERSION,
325
+ transactionDepth: this.#depth,
326
+ fingerprint: mysqlQueryFingerprint(this.#observation, query),
327
+ prepared: prepared !== undefined,
328
+ });
329
+ this.#streams?.add(exposedStream);
330
+ return exposedStream;
331
+ }
332
+ async #validateRows(query, rows) {
333
+ if (!hasQueryResultValidator(query))
334
+ return rows;
335
+ return validateQueryResultRows(query, rows, mysqlQueryFingerprint(this.#observation, query));
336
+ }
337
+ async #validateBatch(queries, results) {
338
+ let validated;
339
+ const queryList = queries;
340
+ const resultList = results;
341
+ for (let index = 0; index < queryList.length; index += 1) {
342
+ const query = queryList[index];
343
+ if (!hasQueryResultValidator(query))
344
+ continue;
345
+ validated ??= [...resultList];
346
+ validated[index] = await validateQueryResultRows(query, resultList[index], mysqlQueryFingerprint(this.#observation, query));
347
+ }
348
+ return (validated ?? results);
349
+ }
350
+ async #loadData(statement, chunks, options) {
351
+ assertExecutionCapabilities(this.executionCapabilities, options);
352
+ this.#assertScopeOpen();
353
+ this.#assertConnectionAvailable("start LOAD DATA");
354
+ const connection = this.#connection ?? (await this.#pool.getConnection());
355
+ if (connection.loadData === undefined) {
356
+ if (this.#connection === undefined)
357
+ connection.release();
358
+ throw new Error("MySQL LOAD DATA requires an adapter with an application-owned local infile stream implementation");
359
+ }
360
+ const state = createMySqlConnectionOperation();
361
+ if (this.#connection !== undefined)
362
+ this.#transactionState.bulk = state;
363
+ let discarded = false;
364
+ const discard = () => {
365
+ if (discarded || connection.destroy === undefined)
366
+ return;
367
+ discarded = true;
368
+ connection.destroy();
369
+ if (this.#transactionState !== undefined) {
370
+ this.#transactionState.usable = false;
371
+ this.#transactionState.discarded = true;
372
+ }
373
+ };
374
+ try {
375
+ await runControlledExecution(options, () => connection.loadData(statement, chunks), discard);
376
+ if (this.#connection === undefined)
377
+ connection.release();
378
+ }
379
+ catch (error) {
380
+ discard();
381
+ if (!discarded && this.#connection === undefined)
382
+ connection.release();
383
+ throw error;
384
+ }
385
+ finally {
386
+ if (this.#transactionState?.bulk === state)
387
+ this.#transactionState.bulk = undefined;
388
+ state.finish();
389
+ }
390
+ }
391
+ prepare(statementName, factory) {
392
+ this.#assertScopeOpen();
393
+ return prepareMySqlQuery(this.#prepared, mysqlRenderer, statementName, factory);
77
394
  }
78
395
  async transaction(fn) {
396
+ if (this.#observation.observer === undefined)
397
+ return this.#transactionUnobserved(fn);
398
+ const observation = startDatabaseObservation(this.#observation.observer, {
399
+ kind: "transaction",
400
+ dialect: "mysql",
401
+ grammarVersion: MYSQL_DIALECT_VERSION,
402
+ transactionDepth: this.#depth + 1,
403
+ });
404
+ if (observation === undefined)
405
+ return this.#transactionUnobserved(fn);
406
+ try {
407
+ const result = await observation.run(() => this.#transactionUnobserved(fn));
408
+ observation.end({ status: "success" });
409
+ return result;
410
+ }
411
+ catch (error) {
412
+ observation.end(databaseErrorCompletion(error), error);
413
+ throw error;
414
+ }
415
+ }
416
+ async #transactionUnobserved(fn) {
417
+ this.#assertScopeOpen();
79
418
  if (this.#connection !== undefined)
80
419
  return this.#nested(fn);
81
420
  const connection = await this.#pool.getConnection();
421
+ let transaction;
422
+ let result;
82
423
  try {
83
424
  await connection.beginTransaction();
84
- const result = await fn(new _a(this.#pool, connection, false, this.#policy, this.#decimal, 1));
425
+ transaction = new _a(this.#pool, connection, false, 1, this.#prepared, this.#decoderPlans, this.#observation, { active: undefined, batch: undefined, bulk: undefined, execute: undefined, discarded: false, usable: true });
426
+ result = await fn(transaction);
427
+ transaction.#scopeOpen = false;
428
+ transaction.#transactionState.usable = false;
429
+ await transaction.#assertTransactionReadyForFinalize(true);
85
430
  await connection.commit();
86
- return result;
87
431
  }
88
432
  catch (error) {
89
- try {
90
- await connection.rollback();
433
+ if (transaction !== undefined) {
434
+ transaction.#scopeOpen = false;
435
+ transaction.#transactionState.usable = false;
436
+ await transaction.#invalidateScope();
91
437
  }
92
- catch {
93
- /* Preserve the original failure. */
438
+ if (transaction === undefined || transaction.#transactionState?.discarded !== true) {
439
+ try {
440
+ await connection.rollback();
441
+ }
442
+ catch {
443
+ /* Preserve the original failure. */
444
+ }
445
+ try {
446
+ connection.release();
447
+ }
448
+ catch {
449
+ /* Preserve the original failure. */
450
+ }
94
451
  }
95
452
  throw error;
96
453
  }
97
- finally {
98
- connection.release();
99
- }
454
+ connection.release();
455
+ return result;
100
456
  }
101
457
  async #nested(fn) {
458
+ this.#assertScopeOpen();
459
+ this.#assertConnectionAvailable("start a nested transaction");
102
460
  const connection = this.#connection;
103
461
  const depth = this.#depth + 1;
104
462
  const savepoint = `typed_sql_${depth}`;
105
463
  await connection.query(`SAVEPOINT ${savepoint}`);
464
+ let transaction;
106
465
  try {
107
- const result = await fn(new _a(this.#pool, connection, false, this.#policy, this.#decimal, depth));
466
+ transaction = new _a(this.#pool, connection, false, depth, this.#prepared, this.#decoderPlans, this.#observation, this.#transactionState);
467
+ const result = await fn(transaction);
468
+ transaction.#scopeOpen = false;
469
+ await transaction.#assertTransactionReadyForFinalize();
108
470
  await connection.query(`RELEASE SAVEPOINT ${savepoint}`);
109
471
  return result;
110
472
  }
111
473
  catch (error) {
112
- await connection.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
474
+ if (transaction !== undefined)
475
+ await transaction.#invalidateScope();
476
+ if (this.#transactionState?.usable !== false) {
477
+ try {
478
+ await connection.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
479
+ }
480
+ catch {
481
+ /* Preserve the original failure. */
482
+ }
483
+ }
113
484
  throw error;
114
485
  }
115
486
  }
487
+ async #rejectActiveStreams() {
488
+ if (this.#streams === undefined || this.#streams.size === 0)
489
+ return;
490
+ const active = [...this.#streams];
491
+ await Promise.allSettled(active.map((stream) => stream.close()));
492
+ throw new Error(`MySQL transaction callback returned with ${active.length} active query stream${active.length === 1 ? "" : "s"}; consume or close every stream before returning`);
493
+ }
494
+ async #closeStreams() {
495
+ const streams = new Set(this.#streams);
496
+ if (this.#transactionState?.active !== undefined)
497
+ streams.add(this.#transactionState.active);
498
+ if (streams.size === 0)
499
+ return;
500
+ await Promise.allSettled([...streams].map((stream) => stream.close()));
501
+ }
502
+ async #invalidateScope() {
503
+ this.#scopeOpen = false;
504
+ await this.#closeStreams();
505
+ await this.#settleExecutes();
506
+ await this.#settleBatch();
507
+ await this.#settleBulk();
508
+ }
509
+ async #assertTransactionReadyForFinalize(connectionFinalizing = false) {
510
+ const leakedExecutes = new Set(this.#executes);
511
+ if (this.#transactionState?.execute !== undefined)
512
+ leakedExecutes.add(this.#transactionState.execute);
513
+ const leakedBatch = this.#transactionState?.batch;
514
+ const leakedBulk = this.#transactionState?.bulk;
515
+ await this.#rejectActiveStreams();
516
+ const active = this.#transactionState?.active;
517
+ if (active !== undefined) {
518
+ await Promise.allSettled([active.close()]);
519
+ throw new Error("MySQL transaction callback returned while a nested query stream owned its connection; await nested work and close every stream before returning");
520
+ }
521
+ if (leakedExecutes.size > 0) {
522
+ await this.#settleExecuteOperations(leakedExecutes);
523
+ throw new Error("MySQL transaction callback returned while an execute operation owned its connection; await execute before returning");
524
+ }
525
+ if (leakedBatch !== undefined) {
526
+ await leakedBatch.completion;
527
+ throw new Error("MySQL transaction callback returned while an ordered batch owned its connection; await the batch before returning");
528
+ }
529
+ if (leakedBulk !== undefined) {
530
+ await leakedBulk.completion;
531
+ throw new Error("MySQL transaction callback returned while LOAD DATA owned its connection; await LOAD DATA before returning");
532
+ }
533
+ if (!connectionFinalizing && this.#transactionState?.usable === false)
534
+ throw new Error("This MySQL transaction connection is no longer active");
535
+ }
536
+ #assertConnectionAvailable(action) {
537
+ if (this.#transactionState?.active !== undefined)
538
+ throw new Error(`Cannot ${action} while a MySQL query stream owns the transaction connection`);
539
+ if (this.#transactionState?.batch !== undefined)
540
+ throw new Error(`Cannot ${action} while a MySQL ordered batch owns the transaction connection`);
541
+ if (this.#transactionState?.bulk !== undefined)
542
+ throw new Error(`Cannot ${action} while MySQL LOAD DATA owns the transaction connection`);
543
+ if (this.#transactionState?.execute !== undefined)
544
+ throw new Error(`Cannot ${action} while a MySQL execute operation owns the transaction connection`);
545
+ }
546
+ #assertBatchCanContinue(operation) {
547
+ this.#assertScopeOpen();
548
+ if (this.#transactionState?.batch !== operation)
549
+ throw new Error("This MySQL ordered batch no longer owns the transaction connection");
550
+ }
551
+ async #settleBatch() {
552
+ const batch = this.#transactionState?.batch;
553
+ if (batch !== undefined)
554
+ await batch.completion;
555
+ }
556
+ async #settleBulk() {
557
+ const bulk = this.#transactionState?.bulk;
558
+ if (bulk !== undefined)
559
+ await bulk.completion;
560
+ }
561
+ async #settleExecutes() {
562
+ const operations = new Set(this.#executes);
563
+ if (this.#transactionState?.execute !== undefined)
564
+ operations.add(this.#transactionState.execute);
565
+ await this.#settleExecuteOperations(operations);
566
+ }
567
+ async #settleExecuteOperations(operations) {
568
+ for (const operation of operations) {
569
+ await operation.completion;
570
+ }
571
+ }
572
+ #assertScopeOpen() {
573
+ if (this.#connection !== undefined && (!this.#scopeOpen || this.#transactionState?.usable === false))
574
+ throw new Error("This MySQL transaction scope is no longer active");
575
+ }
116
576
  async close() {
117
577
  if (this.#connection !== undefined)
118
578
  throw new Error("Cannot close a database from inside a transaction");
@@ -122,9 +582,9 @@ class MySqlDatabaseImplementation {
122
582
  }
123
583
  _a = MySqlDatabaseImplementation;
124
584
  export function createMySqlDatabase(options) {
125
- const policy = { ...defaultRuntimePolicy, ...options.typePolicy };
126
- if (policy.decimal === "Decimal" && options.decimal === undefined)
585
+ const typePolicy = { ...defaultRuntimeTypePolicy, ...options.typePolicy };
586
+ if (typePolicy.decimal === "Decimal" && options.decimal === undefined)
127
587
  throw new TypeError("decimal=Decimal requires a decimal(value) codec");
128
- return new MySqlDatabaseImplementation(options.pool, undefined, options.ownsPool ?? false, policy, options.decimal, 0);
588
+ return new MySqlDatabaseImplementation(options.pool, undefined, options.ownsPool ?? false, 0, createMySqlPreparedQueryState(), new MySqlDecoderPlanCache(typePolicy, options.decimal), createMySqlObservationState(options.observer));
129
589
  }
130
590
  //# sourceMappingURL=runtime.js.map