@nextlyhq/adapter-mysql 0.0.2-alpha.72 → 0.0.2-alpha.73

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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DrizzleAdapter } from "@nextlyhq/adapter-drizzle";
2
- import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SqlParam, SslConfig, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
2
+ import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SessionOptions, SqlParam, SslConfig, StatementSession, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
3
3
  import { AnyRelations } from "drizzle-orm";
4
4
  import { MySql2Database } from "drizzle-orm/mysql2";
5
5
  /**
@@ -45,6 +45,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
45
45
  * Connection pool instance.
46
46
  */
47
47
  private pool;
48
+ /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */
49
+ private readonly sessions;
48
50
  /**
49
51
  * Connection state flag.
50
52
  */
@@ -54,6 +56,7 @@ export declare class MySqlAdapter extends DrizzleAdapter {
54
56
  * string at connect. Undefined until then, which callers treat as not known.
55
57
  */
56
58
  private sharedRowLock;
59
+ private functionalIndexes;
57
60
  /**
58
61
  * Creates a new MySQL adapter instance.
59
62
  *
@@ -78,7 +81,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
78
81
  *
79
82
  * @remarks
80
83
  * This method is idempotent - calling it multiple times is safe.
81
- * It waits for all connections to be released before shutting down.
84
+ * It waits for all connections to be released before shutting down,
85
+ * including each running `withSession`'s connection of its own.
82
86
  */
83
87
  disconnect(): Promise<void>;
84
88
  /**
@@ -121,6 +125,27 @@ export declare class MySqlAdapter extends DrizzleAdapter {
121
125
  * MySQL's quirks with nested transactions.
122
126
  */
123
127
  transaction<T>(work: (tx: TransactionContext) => Promise<T>, options?: TransactionOptions): Promise<T>;
128
+ /**
129
+ * Runs `work` on a connection of its own, opened for it with the pool's
130
+ * connection settings, outside a transaction, each statement committing as
131
+ * it runs.
132
+ *
133
+ * @remarks
134
+ * Not a pooled connection: on a server whose sessions start with autocommit
135
+ * off, one can hold a transaction an earlier caller left open, and turning
136
+ * autocommit on would commit that caller's writes. A new connection holds
137
+ * nothing. It does not count against `pool.max`.
138
+ *
139
+ * Each statement commits as it runs, whatever the server's own autocommit
140
+ * default. With `strictSqlMode` the session runs in strict mode, as a
141
+ * transaction given the same option does, so a statement that would cut a
142
+ * value short fails and changes nothing. The connection is closed
143
+ * afterwards, whether `work` finished or threw, so whatever `work` set on
144
+ * its session (a temporary table, a session variable, a lock) ends with it.
145
+ */
146
+ override withSession<T>(work: (session: StatementSession) => Promise<T>, options?: SessionOptions): Promise<T>;
147
+ /** `withSession`'s work, on a connection it opens and closes. */
148
+ private runSession;
124
149
  /** The attempts `transaction` makes, each on a connection of its own. */
125
150
  private runTransactionAttempts;
126
151
  /**
@@ -185,12 +210,29 @@ export declare class MySqlAdapter extends DrizzleAdapter {
185
210
  * Builds mysql2 Pool configuration from adapter config.
186
211
  */
187
212
  private buildPoolConfig;
213
+ /**
214
+ * Builds the settings every connection opens with, pooled or not, from
215
+ * adapter config.
216
+ */
217
+ private buildConnectionConfig;
218
+ /** Where to connect: the connection string, or the explicit options. */
219
+ private buildAddressConfig;
220
+ /** mysql2's `ssl` option for the adapter's `ssl` setting. */
221
+ private buildSslConfig;
188
222
  /**
189
223
  * Begins a transaction with the specified options, recording in `changed`
190
224
  * each session setting it changes, as it changes it, for `restoreSession`
191
225
  * to put back.
192
226
  */
193
227
  private beginTransaction;
228
+ /**
229
+ * Strict mode for the rest of this connection's session: an INSERT, UPDATE
230
+ * or ALTER that would cut a string to its column's length, or clamp a
231
+ * number to its range, fails instead of applying with a warning. Added to
232
+ * the session's own mode, so every other flag it had still holds, and
233
+ * recorded in `changed` for `restoreSession` to put back.
234
+ */
235
+ private enterStrictMode;
194
236
  /**
195
237
  * Creates a TransactionContext for the given connection.
196
238
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DrizzleAdapter } from "@nextlyhq/adapter-drizzle";
2
- import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SqlParam, SslConfig, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
2
+ import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SessionOptions, SqlParam, SslConfig, StatementSession, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
3
3
  import { AnyRelations } from "drizzle-orm";
4
4
  import { MySql2Database } from "drizzle-orm/mysql2";
5
5
  /**
@@ -45,6 +45,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
45
45
  * Connection pool instance.
46
46
  */
47
47
  private pool;
48
+ /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */
49
+ private readonly sessions;
48
50
  /**
49
51
  * Connection state flag.
50
52
  */
@@ -54,6 +56,7 @@ export declare class MySqlAdapter extends DrizzleAdapter {
54
56
  * string at connect. Undefined until then, which callers treat as not known.
55
57
  */
56
58
  private sharedRowLock;
59
+ private functionalIndexes;
57
60
  /**
58
61
  * Creates a new MySQL adapter instance.
59
62
  *
@@ -78,7 +81,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
78
81
  *
79
82
  * @remarks
80
83
  * This method is idempotent - calling it multiple times is safe.
81
- * It waits for all connections to be released before shutting down.
84
+ * It waits for all connections to be released before shutting down,
85
+ * including each running `withSession`'s connection of its own.
82
86
  */
83
87
  disconnect(): Promise<void>;
84
88
  /**
@@ -121,6 +125,27 @@ export declare class MySqlAdapter extends DrizzleAdapter {
121
125
  * MySQL's quirks with nested transactions.
122
126
  */
123
127
  transaction<T>(work: (tx: TransactionContext) => Promise<T>, options?: TransactionOptions): Promise<T>;
128
+ /**
129
+ * Runs `work` on a connection of its own, opened for it with the pool's
130
+ * connection settings, outside a transaction, each statement committing as
131
+ * it runs.
132
+ *
133
+ * @remarks
134
+ * Not a pooled connection: on a server whose sessions start with autocommit
135
+ * off, one can hold a transaction an earlier caller left open, and turning
136
+ * autocommit on would commit that caller's writes. A new connection holds
137
+ * nothing. It does not count against `pool.max`.
138
+ *
139
+ * Each statement commits as it runs, whatever the server's own autocommit
140
+ * default. With `strictSqlMode` the session runs in strict mode, as a
141
+ * transaction given the same option does, so a statement that would cut a
142
+ * value short fails and changes nothing. The connection is closed
143
+ * afterwards, whether `work` finished or threw, so whatever `work` set on
144
+ * its session (a temporary table, a session variable, a lock) ends with it.
145
+ */
146
+ override withSession<T>(work: (session: StatementSession) => Promise<T>, options?: SessionOptions): Promise<T>;
147
+ /** `withSession`'s work, on a connection it opens and closes. */
148
+ private runSession;
124
149
  /** The attempts `transaction` makes, each on a connection of its own. */
125
150
  private runTransactionAttempts;
126
151
  /**
@@ -185,12 +210,29 @@ export declare class MySqlAdapter extends DrizzleAdapter {
185
210
  * Builds mysql2 Pool configuration from adapter config.
186
211
  */
187
212
  private buildPoolConfig;
213
+ /**
214
+ * Builds the settings every connection opens with, pooled or not, from
215
+ * adapter config.
216
+ */
217
+ private buildConnectionConfig;
218
+ /** Where to connect: the connection string, or the explicit options. */
219
+ private buildAddressConfig;
220
+ /** mysql2's `ssl` option for the adapter's `ssl` setting. */
221
+ private buildSslConfig;
188
222
  /**
189
223
  * Begins a transaction with the specified options, recording in `changed`
190
224
  * each session setting it changes, as it changes it, for `restoreSession`
191
225
  * to put back.
192
226
  */
193
227
  private beginTransaction;
228
+ /**
229
+ * Strict mode for the rest of this connection's session: an INSERT, UPDATE
230
+ * or ALTER that would cut a string to its column's length, or clamp a
231
+ * number to its range, fails instead of applying with a warning. Added to
232
+ * the session's own mode, so every other flag it had still holds, and
233
+ * recorded in `changed` for `restoreSession` to put back.
234
+ */
235
+ private enterStrictMode;
194
236
  /**
195
237
  * Creates a TransactionContext for the given connection.
196
238
  *
package/dist/index.mjs CHANGED
@@ -236,6 +236,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
236
236
  * Connection pool instance.
237
237
  */
238
238
  pool = null;
239
+ /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */
240
+ sessions = /* @__PURE__ */ new Set();
239
241
  /**
240
242
  * Connection state flag.
241
243
  */
@@ -245,6 +247,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
245
247
  * string at connect. Undefined until then, which callers treat as not known.
246
248
  */
247
249
  sharedRowLock;
250
+ functionalIndexes;
248
251
  /**
249
252
  * Creates a new MySQL adapter instance.
250
253
  *
@@ -275,8 +278,9 @@ var MySqlAdapter = class extends DrizzleAdapter {
275
278
  const connection = await this.pool.getConnection();
276
279
  try {
277
280
  await connection.query("SELECT 1");
278
- const { variant } = await checkDialectVersion(connection, "mysql", { onWarning: (msg) => this.config.logger?.warn?.(msg) });
281
+ const { variant, functionalIndexes } = await checkDialectVersion(connection, "mysql", { onWarning: (msg) => this.config.logger?.warn?.(msg) });
279
282
  this.sharedRowLock = acceptsForShare(variant);
283
+ this.functionalIndexes = functionalIndexes;
280
284
  this.connected = true;
281
285
  if (this.config.logger?.info) this.config.logger.info("MySQL connection established", {
282
286
  host: this.config.host ?? "from URL",
@@ -299,7 +303,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
299
303
  *
300
304
  * @remarks
301
305
  * This method is idempotent - calling it multiple times is safe.
302
- * It waits for all connections to be released before shutting down.
306
+ * It waits for all connections to be released before shutting down,
307
+ * including each running `withSession`'s connection of its own.
303
308
  */
304
309
  async disconnect() {
305
310
  const pool = this.pool;
@@ -308,6 +313,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
308
313
  this.drizzleByRelations = /* @__PURE__ */ new WeakMap();
309
314
  if (!pool) return;
310
315
  try {
316
+ await Promise.allSettled([...this.sessions]);
311
317
  await pool.end();
312
318
  if (this.config.logger?.info) this.config.logger.info("MySQL connection closed");
313
319
  } finally {
@@ -389,6 +395,64 @@ var MySqlAdapter = class extends DrizzleAdapter {
389
395
  async transaction(work, options) {
390
396
  return this.runInFlowTransaction(() => this.runTransactionAttempts(work, options));
391
397
  }
398
+ /**
399
+ * Runs `work` on a connection of its own, opened for it with the pool's
400
+ * connection settings, outside a transaction, each statement committing as
401
+ * it runs.
402
+ *
403
+ * @remarks
404
+ * Not a pooled connection: on a server whose sessions start with autocommit
405
+ * off, one can hold a transaction an earlier caller left open, and turning
406
+ * autocommit on would commit that caller's writes. A new connection holds
407
+ * nothing. It does not count against `pool.max`.
408
+ *
409
+ * Each statement commits as it runs, whatever the server's own autocommit
410
+ * default. With `strictSqlMode` the session runs in strict mode, as a
411
+ * transaction given the same option does, so a statement that would cut a
412
+ * value short fails and changes nothing. The connection is closed
413
+ * afterwards, whether `work` finished or threw, so whatever `work` set on
414
+ * its session (a temporary table, a session variable, a lock) ends with it.
415
+ */
416
+ async withSession(work, options) {
417
+ this.refuseSessionInTransaction();
418
+ this.ensurePool();
419
+ const running = this.runSession(work, options);
420
+ this.sessions.add(running);
421
+ try {
422
+ return await running;
423
+ } finally {
424
+ this.sessions.delete(running);
425
+ }
426
+ }
427
+ /** `withSession`'s work, on a connection it opens and closes. */
428
+ async runSession(work, options) {
429
+ let connection;
430
+ try {
431
+ connection = await mysql.createConnection(this.buildConnectionConfig());
432
+ } catch (error) {
433
+ throw this.classifyError(error);
434
+ }
435
+ try {
436
+ try {
437
+ await connection.query("SET SESSION autocommit = 1");
438
+ if (options?.strictSqlMode) await this.enterStrictMode(connection, {});
439
+ } catch (error) {
440
+ throw this.classifyError(error);
441
+ }
442
+ return await work({ execute: async (statement, params = []) => {
443
+ const startTime = Date.now();
444
+ try {
445
+ const [rows] = await connection.query(statement, params);
446
+ if (this.config.logger?.query) this.config.logger.query(statement, params, Date.now() - startTime);
447
+ return rows;
448
+ } catch (error) {
449
+ throw this.classifyError(error, statement);
450
+ }
451
+ } });
452
+ } finally {
453
+ connection.destroy();
454
+ }
455
+ }
392
456
  /** The attempts `transaction` makes, each on a connection of its own. */
393
457
  async runTransactionAttempts(work, options) {
394
458
  const pool = this.ensurePool();
@@ -459,7 +523,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
459
523
  supportsOnConflict: true,
460
524
  maxParamsPerQuery: 65535,
461
525
  maxIdentifierLength: 64,
462
- sharedRowLock: this.sharedRowLock
526
+ sharedRowLock: this.sharedRowLock,
527
+ functionalIndexes: this.functionalIndexes
463
528
  };
464
529
  }
465
530
  /**
@@ -558,35 +623,51 @@ var MySqlAdapter = class extends DrizzleAdapter {
558
623
  * Builds mysql2 Pool configuration from adapter config.
559
624
  */
560
625
  buildPoolConfig() {
561
- const config = {};
562
- if (this.config.url) config.uri = this.config.url;
563
- else {
564
- if (this.config.host) config.host = this.config.host;
565
- if (this.config.port) config.port = this.config.port;
566
- if (this.config.database) config.database = this.config.database;
567
- if (this.config.user) config.user = this.config.user;
568
- if (this.config.password) config.password = this.config.password;
569
- }
626
+ const config = this.buildConnectionConfig();
570
627
  config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;
571
628
  config.idleTimeout = this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;
572
- config.connectTimeout = this.config.pool?.connectionTimeoutMs ?? DEFAULT_POOL_CONFIG.connectionTimeoutMs;
573
629
  config.waitForConnections = true;
574
630
  config.queueLimit = 0;
575
- if (this.config.ssl) {
576
- if (typeof this.config.ssl === "boolean") config.ssl = this.config.ssl ? {} : void 0;
577
- else config.ssl = {
578
- rejectUnauthorized: this.config.ssl.rejectUnauthorized,
579
- ca: this.config.ssl.ca,
580
- cert: this.config.ssl.cert,
581
- key: this.config.ssl.key
582
- };
583
- }
631
+ return config;
632
+ }
633
+ /**
634
+ * Builds the settings every connection opens with, pooled or not, from
635
+ * adapter config.
636
+ */
637
+ buildConnectionConfig() {
638
+ const config = this.buildAddressConfig();
639
+ config.connectTimeout = this.config.pool?.connectionTimeoutMs ?? DEFAULT_POOL_CONFIG.connectionTimeoutMs;
640
+ if (this.config.ssl) config.ssl = this.buildSslConfig(this.config.ssl);
584
641
  if (this.config.timezone) config.timezone = this.config.timezone;
585
642
  if (this.config.charset) config.charset = this.config.charset;
586
643
  config.multipleStatements = false;
587
644
  config.dateStrings = false;
588
645
  return config;
589
646
  }
647
+ /** Where to connect: the connection string, or the explicit options. */
648
+ buildAddressConfig() {
649
+ if (this.config.url) return { uri: this.config.url };
650
+ const { host, port, database, user, password } = this.config;
651
+ const config = {
652
+ host,
653
+ port,
654
+ database,
655
+ user,
656
+ password
657
+ };
658
+ for (const key of Object.keys(config)) if (!config[key]) delete config[key];
659
+ return config;
660
+ }
661
+ /** mysql2's `ssl` option for the adapter's `ssl` setting. */
662
+ buildSslConfig(ssl) {
663
+ if (typeof ssl === "boolean") return ssl ? {} : void 0;
664
+ return {
665
+ rejectUnauthorized: ssl.rejectUnauthorized,
666
+ ca: ssl.ca,
667
+ cert: ssl.cert,
668
+ key: ssl.key
669
+ };
670
+ }
590
671
  /**
591
672
  * Begins a transaction with the specified options, recording in `changed`
592
673
  * each session setting it changes, as it changes it, for `restoreSession`
@@ -611,12 +692,20 @@ var MySqlAdapter = class extends DrizzleAdapter {
611
692
  await connection.query(`SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`);
612
693
  changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;
613
694
  }
614
- if (options?.strictSqlMode) {
615
- const [rows] = await connection.query("SELECT @@SESSION.sql_mode AS mode");
616
- const previous = rows[0]?.mode;
617
- await connection.query("SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))");
618
- changed.sqlMode = typeof previous === "string" ? previous : null;
619
- }
695
+ if (options?.strictSqlMode) await this.enterStrictMode(connection, changed);
696
+ }
697
+ /**
698
+ * Strict mode for the rest of this connection's session: an INSERT, UPDATE
699
+ * or ALTER that would cut a string to its column's length, or clamp a
700
+ * number to its range, fails instead of applying with a warning. Added to
701
+ * the session's own mode, so every other flag it had still holds, and
702
+ * recorded in `changed` for `restoreSession` to put back.
703
+ */
704
+ async enterStrictMode(connection, changed) {
705
+ const [rows] = await connection.query("SELECT @@SESSION.sql_mode AS mode");
706
+ const previous = rows[0]?.mode;
707
+ changed.sqlMode = typeof previous === "string" ? previous : null;
708
+ await connection.query("SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))");
620
709
  }
621
710
  /**
622
711
  * Creates a TransactionContext for the given connection.