@nextlyhq/adapter-mysql 0.0.2-alpha.74 → 0.0.2-alpha.75
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.cjs +53 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.mjs +53 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -402,6 +402,59 @@ var MySqlAdapter = class extends _nextlyhq_adapter_drizzle.DrizzleAdapter {
|
|
|
402
402
|
};
|
|
403
403
|
}
|
|
404
404
|
/**
|
|
405
|
+
* The connection as a health check reads it, on one pooled connection: the
|
|
406
|
+
* server's version, timed for the round trip, then that session's
|
|
407
|
+
* `Ssl_cipher` status, which names the cipher when the session's connection
|
|
408
|
+
* is encrypted and is empty when it is not.
|
|
409
|
+
*
|
|
410
|
+
* @remarks
|
|
411
|
+
* The status and the connection's own socket are weighed by
|
|
412
|
+
* `encryptionOf`: behind a proxy the status can describe the proxy's own
|
|
413
|
+
* connection to the server, while this connection's socket is TLS. The
|
|
414
|
+
* socket is read as it is, never inferred from the `ssl` setting.
|
|
415
|
+
*
|
|
416
|
+
* Whether the database is on this machine is judged from the configured
|
|
417
|
+
* address, which never leaves this method. Any failure answers `"unknown"`.
|
|
418
|
+
*/
|
|
419
|
+
async inspectConnection() {
|
|
420
|
+
const local = this.isLocalHost(this.connectionHost());
|
|
421
|
+
let connection;
|
|
422
|
+
try {
|
|
423
|
+
connection = await this.ensurePool().getConnection();
|
|
424
|
+
const started = performance.now();
|
|
425
|
+
const [versionRows] = await connection.query("SELECT VERSION() AS version");
|
|
426
|
+
const roundTripMs = Math.max(0, Math.round(performance.now() - started));
|
|
427
|
+
const [statusRows] = await connection.query("SHOW SESSION STATUS LIKE 'Ssl_cipher'");
|
|
428
|
+
const cipher = statusRows[0]?.Value;
|
|
429
|
+
const version = versionRows[0]?.version;
|
|
430
|
+
return {
|
|
431
|
+
encryption: this.encryptionOf({
|
|
432
|
+
session: typeof cipher === "string" ? cipher !== "" : void 0,
|
|
433
|
+
ownSocketTls: this.ownSocketIsTls(connection)
|
|
434
|
+
}),
|
|
435
|
+
local,
|
|
436
|
+
roundTripMs,
|
|
437
|
+
...typeof version === "string" && version !== "" ? { serverVersion: version.split("-")[0] } : {}
|
|
438
|
+
};
|
|
439
|
+
} catch {
|
|
440
|
+
return {
|
|
441
|
+
encryption: "unknown",
|
|
442
|
+
local
|
|
443
|
+
};
|
|
444
|
+
} finally {
|
|
445
|
+
connection?.release();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The host this adapter connects to, as mysql2 resolves it: the connection
|
|
450
|
+
* string's when one is set, else the `host` option, else `localhost`.
|
|
451
|
+
* `undefined` when the connection string cannot be read.
|
|
452
|
+
*/
|
|
453
|
+
connectionHost() {
|
|
454
|
+
const configured = this.config.url ? this.hostOfUrl(this.config.url) : this.config.host ?? "";
|
|
455
|
+
return configured === "" ? "localhost" : configured;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
405
458
|
* Execute a raw SQL query.
|
|
406
459
|
*
|
|
407
460
|
* @param sql - SQL query string with ? placeholders
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["poolWaitTimeoutError","DrizzleAdapter","mysql","checkDialectVersion","acceptsForShare","drizzle","createDatabaseError","and","eq","getColumns","getTableConfig","isDatabaseError"],"sources":["../src/pool-wait.ts","../src/index.ts"],"sourcesContent":["/**\n * The bounded wait for a pooled connection.\n *\n * mysql2 has no such bound. A request that finds every connection in use is\n * queued until one is released, however long that takes, so a database that\n * stays busy holds every request behind it indefinitely. This bounds the wait\n * the way pg-pool's `connectionTimeoutMillis` does: past the bound, the\n * request fails with the adapters' own pool-wait timeout error, which core\n * answers with \"database busy\".\n *\n * Only the wait in the queue: a request that opens a new connection is bounded\n * by mysql2's `connectTimeout` instead, set from the same setting, and a\n * connection that cannot be opened is a database that cannot be reached\n * rather than one that is busy.\n */\nimport { poolWaitTimeoutError } from \"@nextlyhq/adapter-drizzle/types\";\n\n/** A connection the pool hands out, as far as the bound needs one. */\ninterface QueuedConnection {\n release(): void;\n}\n\ntype GetConnectionCallback = (\n err: Error | null,\n connection?: QueuedConnection\n) => void;\n\n/**\n * The part of mysql2's callback pool the bound uses. Declared here because\n * mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\nexport interface QueueingPool {\n getConnection(callback: GetConnectionCallback): void;\n once(event: \"enqueue\", listener: () => void): unknown;\n removeListener(event: \"enqueue\", listener: () => void): unknown;\n /** mysql2's queue of waiting requests (a Denque). */\n _connectionQueue?: {\n length: number;\n peekAt(index: number): unknown;\n removeOne(index: number): unknown;\n };\n}\n\n/**\n * Takes a request that gave up out of the pool's queue, so a pool that stays\n * saturated does not collect every request that has already failed, and a\n * released connection goes to one still waiting. Returns whether it was\n * there: with diagnostics tracing on, mysql2 queues a wrapper of the\n * callback rather than the callback itself, and then it stays queued until a\n * connection reaches it.\n */\nfunction leaveQueue(pool: QueueingPool, waiting: GetConnectionCallback) {\n const queue = pool._connectionQueue;\n if (!queue) return false;\n for (let at = 0; at < queue.length; at++) {\n if (queue.peekAt(at) === waiting) {\n queue.removeOne(at);\n return true;\n }\n }\n return false;\n}\n\n/**\n * Makes `pool` fail a request that has waited `boundMs` in its queue for a\n * connection, and returns it. A bound of 0 leaves the wait unbounded, as\n * pg-pool reads a `connectionTimeoutMillis` of 0.\n *\n * Replaces the pool's own `getConnection`, which its `query` and `execute`\n * go through, so every path to a connection is bounded: the adapter's\n * statements, its transactions, and the Drizzle handle core services query\n * through.\n *\n * A request that gives up leaves the queue. Where mysql2 cannot be asked to\n * drop it, the connection it is eventually handed is released straight back\n * rather than held by a request that has already failed.\n */\nexport function boundPoolWait<P extends QueueingPool>(\n pool: P,\n boundMs: number\n): P {\n if (boundMs <= 0) return pool;\n const getConnection = pool.getConnection.bind(pool);\n const bounded = (callback: GetConnectionCallback) => {\n let answered = false;\n // Set only once the request has queued; read by the answer that ends it.\n const wait: { timer?: ReturnType<typeof setTimeout> } = {};\n // mysql2 emits `enqueue` synchronously, inside this call, exactly when\n // the request joins the queue rather than taking or opening a connection.\n let queued = false;\n const onEnqueue = () => (queued = true);\n pool.once(\"enqueue\", onEnqueue);\n const waiting: GetConnectionCallback = (err, connection) => {\n if (answered) {\n connection?.release();\n return;\n }\n answered = true;\n clearTimeout(wait.timer);\n callback(err, connection);\n };\n getConnection(waiting);\n pool.removeListener(\"enqueue\", onEnqueue);\n if (!queued || answered) return;\n wait.timer = setTimeout(() => {\n answered = true;\n leaveQueue(pool, waiting);\n callback(poolWaitTimeoutError(boundMs));\n }, boundMs);\n };\n pool.getConnection = bounded;\n return pool;\n}\n","/**\n * @nextlyhq/adapter-mysql\n *\n * MySQL database adapter for Nextly.\n * Extends DrizzleAdapter from @nextlyhq/adapter-drizzle to provide MySQL-specific functionality.\n *\n * @remarks\n * This adapter uses the mysql2 package for database connectivity and provides:\n * - Connection pooling via mysql2 Pool\n * - Full transaction support with isolation levels\n * - CRUD operations with workarounds for missing RETURNING clause\n * - MySQL-specific error classification\n * - Automatic retry for deadlocks (error 1213)\n *\n * @example\n * ```typescript\n * import { createMySqlAdapter } from '@nextlyhq/adapter-mysql';\n *\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * });\n *\n * await adapter.connect();\n *\n * // Query data\n * const users = await adapter.select('users', {\n * where: { and: [{ column: 'status', op: '=', value: 'active' }] },\n * limit: 10,\n * });\n *\n * await adapter.disconnect();\n * ```\n *\n * @packageDocumentation\n */\n\nimport { DrizzleAdapter } from \"@nextlyhq/adapter-drizzle\";\n// F17: connect-time DB version check shared across all adapters.\nimport {\n createDatabaseError,\n isDatabaseError,\n type MySqlAdapterConfig,\n type DatabaseCapabilities,\n type PoolStats,\n type TransactionContext,\n type TransactionOptions,\n type SessionOptions,\n type StatementSession,\n type SqlParam,\n type WhereClause,\n type WhereCondition,\n type WhereOperator,\n type SelectOptions,\n type InsertOptions,\n type UpdateOptions,\n type DeleteOptions,\n type UpsertOptions,\n type OrderBySpec,\n type JoinSpec,\n type DatabaseError,\n type DatabaseErrorKind,\n type BaseAdapterConfig,\n type AdapterLogger,\n type PoolConfig,\n type SslConfig,\n} from \"@nextlyhq/adapter-drizzle/types\";\nimport {\n acceptsForShare,\n checkDialectVersion,\n} from \"@nextlyhq/adapter-drizzle/version-check\";\nimport { and, eq, getColumns } from \"drizzle-orm\";\nimport type { AnyRelations, Column, SQL } from \"drizzle-orm\";\nimport { getTableConfig, type MySqlTable } from \"drizzle-orm/mysql-core\";\nimport { drizzle, type MySql2Database } from \"drizzle-orm/mysql2\";\nimport type {\n Pool as CallbackPool,\n Connection as CallbackConnection,\n} from \"mysql2\";\nimport mysql from \"mysql2/promise\";\nimport type {\n ConnectionOptions,\n PoolOptions,\n RowDataPacket,\n ResultSetHeader,\n} from \"mysql2/promise\";\n\nimport { boundPoolWait, type QueueingPool } from \"./pool-wait\";\n\n// mysql2 type definitions use mixin patterns that TypeScript struggles with.\n// We define explicit interfaces for the methods we need.\n\n/**\n * Query result type - either rows or a result header\n */\ntype QueryResult = RowDataPacket[] | RowDataPacket[][] | ResultSetHeader;\n\n/**\n * Queryable interface for mysql2 connections\n */\ninterface Queryable {\n query<T extends QueryResult>(sql: string): Promise<[T, unknown]>;\n query<T extends QueryResult>(\n sql: string,\n values: unknown[]\n ): Promise<[T, unknown]>;\n}\n\n/**\n * mysql2 Pool interface with query method\n */\ninterface Pool extends Queryable {\n getConnection(): Promise<PoolConnection>;\n end(): Promise<void>;\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n}\n\n/**\n * A mysql2 connection of its own, outside the pool\n */\ninterface OwnConnection extends Queryable {\n /** Closes the connection. */\n destroy(): void;\n}\n\n/**\n * mysql2 PoolConnection interface with query and release methods\n */\ninterface PoolConnection extends Queryable {\n /** Returns the connection to the pool, for the next request to use. */\n release(): void;\n /** Closes the connection and removes it from the pool. */\n destroy(): void;\n}\n\n/**\n * What a transaction changed on its connection's session, to put back before\n * the connection returns to the pool; null when it changed nothing.\n *\n * MySQL has no transaction-scoped form of `innodb_lock_wait_timeout` or\n * `sql_mode`: a `SET SESSION` outlives the transaction, and the pooled\n * connection would carry one transaction's setting into every later query\n * handed it. Each field present is the session's value from before the\n * change, or null when it could not be read, and then the connection cannot\n * be put back.\n */\ntype SessionRestore = {\n lockWaitTimeout?: number | null;\n sqlMode?: string | null;\n} | null;\n\n/**\n * Puts back what a transaction changed on its connection's session. False\n * when that could not be done, and the connection must not be pooled.\n */\nasync function restoreSession(\n connection: PoolConnection,\n restore: SessionRestore\n): Promise<boolean> {\n if (restore === null) return true;\n if (restore.lockWaitTimeout === null || restore.sqlMode === null) {\n return false;\n }\n try {\n if (restore.lockWaitTimeout !== undefined) {\n // An integer read from the server, never caller input.\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`\n );\n }\n if (restore.sqlMode !== undefined) {\n // Read from the server, and bound as a value all the same.\n await connection.query(\"SET SESSION sql_mode = ?\", [restore.sqlMode]);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ends a transaction's hold on its connection: back to the pool once its\n * session is as the transaction found it, or closed when it is not. A\n * connection whose ROLLBACK failed (`rollbackFailed`) may still be inside\n * the transaction, and the next request handed it would write into a\n * transaction nobody commits.\n */\nasync function returnTransactionConnection(\n connection: PoolConnection,\n rollbackFailed: boolean,\n restore: SessionRestore\n): Promise<void> {\n if (!rollbackFailed && (await restoreSession(connection, restore))) {\n connection.release();\n } else {\n connection.destroy();\n }\n}\n\n/**\n * A newly opened pooled connection, as far as the time-zone pin needs one.\n *\n * The callback form of `query` is load-bearing twice: a pin that failed with\n * nothing handling the error would end up an unhandled `error` event on the\n * connection, and a failure swallowed without this `destroy` would leave an\n * unpinned session in the pool to serve shifted values.\n */\ninterface PinnableConnection {\n query(sql: string, callback: (error: Error | null) => void): void;\n destroy(): void;\n}\n\n/**\n * The part of mysql2's callback pool the time-zone pin uses. Declared here\n * because mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\ninterface PinnablePool {\n on(\n event: \"connection\",\n listener: (connection: PinnableConnection) => void\n ): unknown;\n}\n\n/**\n * mysql2 accepts only `local`, `Z` and `±HH:MM` spellings for its driver\n * zone, and the ones that resolve to UTC are exactly these. `local` resolves\n * to the Node process's own zone, so it agrees with a UTC session only on a\n * UTC host.\n */\nfunction isUtcDriverZone(zone: string): boolean {\n if (zone === \"local\") return new Date().getTimezoneOffset() === 0;\n return zone === \"Z\" || zone === \"+00:00\" || zone === \"-00:00\";\n}\n\n// Re-export types for convenience\nexport type {\n MySqlAdapterConfig,\n DatabaseCapabilities,\n PoolStats,\n TransactionContext,\n TransactionOptions,\n SqlParam,\n WhereClause,\n WhereCondition,\n WhereOperator,\n SelectOptions,\n InsertOptions,\n UpdateOptions,\n DeleteOptions,\n UpsertOptions,\n OrderBySpec,\n JoinSpec,\n DatabaseError,\n DatabaseErrorKind,\n BaseAdapterConfig,\n AdapterLogger,\n PoolConfig,\n SslConfig,\n};\n\n/**\n * Package version\n */\nexport const VERSION = \"0.1.0\";\n\n/**\n * Default pool configuration values.\n */\nconst DEFAULT_POOL_CONFIG = {\n min: 2,\n max: 10,\n idleTimeoutMs: 30000,\n // How long a request waits for a connection, in the queue or opening one:\n // the same bound, and the same default, as the PostgreSQL adapter's.\n connectionTimeoutMs: 15000,\n};\n\n/**\n * MySQL error codes mapping to DatabaseErrorKind.\n *\n * @see https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html\n */\nconst MYSQL_ERROR_CODES: Record<number, DatabaseErrorKind> = {\n // Unique/Duplicate key violations\n 1022: \"unique_violation\", // ER_DUP_KEY\n 1062: \"unique_violation\", // ER_DUP_ENTRY\n 1169: \"unique_violation\", // ER_DUP_UNIQUE\n 1586: \"unique_violation\", // ER_DUP_ENTRY_WITH_KEY_NAME\n\n // Foreign key violations\n 1216: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW\n 1217: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED\n 1451: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED_2\n 1452: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW_2\n\n // Not null violations\n 1048: \"not_null_violation\", // ER_BAD_NULL_ERROR\n 1364: \"not_null_violation\", // ER_NO_DEFAULT_FOR_FIELD\n\n // Check constraint violations (MySQL 8.0.16+)\n 3819: \"check_violation\", // ER_CHECK_CONSTRAINT_VIOLATED\n\n // Deadlock\n 1213: \"deadlock\", // ER_LOCK_DEADLOCK\n\n // Timeout\n 1205: \"timeout\", // ER_LOCK_WAIT_TIMEOUT\n\n // Connection errors\n 1040: \"connection\", // ER_CON_COUNT_ERROR - Too many connections\n 1042: \"connection\", // ER_BAD_HOST_ERROR\n 1043: \"connection\", // ER_HANDSHAKE_ERROR\n 1044: \"connection\", // ER_DBACCESS_DENIED_ERROR\n 1045: \"connection\", // ER_ACCESS_DENIED_ERROR\n 1129: \"connection\", // ER_HOST_IS_BLOCKED\n 1130: \"connection\", // ER_HOST_NOT_PRIVILEGED\n 2002: \"connection\", // CR_CONNECTION_ERROR\n 2003: \"connection\", // CR_CONN_HOST_ERROR\n 2006: \"connection\", // CR_SERVER_GONE_ERROR\n 2013: \"connection\", // CR_SERVER_LOST\n\n // Query errors\n 1064: \"query\", // ER_PARSE_ERROR\n 1146: \"query\", // ER_NO_SUCH_TABLE\n 1054: \"query\", // ER_BAD_FIELD_ERROR\n};\n\n/** ER_LOCK_DEADLOCK: the transaction lost a race, and running it again may succeed. */\nconst DEADLOCK = 1213;\n\n/**\n * The MySQL error number an error carries: its own, or that of the driver\n * error it wraps, as a classified or a Drizzle query error does.\n */\nfunction errnoOf(error: unknown): number | undefined {\n for (let at: unknown = error, hops = 0; hops < 4; hops++) {\n if (typeof at !== \"object\" || at === null) return undefined;\n const errno: unknown = Reflect.get(at, \"errno\");\n if (typeof errno === \"number\") return errno;\n at = Reflect.get(at, \"cause\");\n }\n return undefined;\n}\n\n/**\n * MySQL database adapter for Nextly.\n *\n * Extends the base DrizzleAdapter to provide MySQL-specific functionality\n * using the mysql2 package.\n *\n * @remarks\n * MySQL has some limitations compared to PostgreSQL:\n * - No native RETURNING clause (requires INSERT then SELECT)\n * - No native ILIKE (uses LOWER() LIKE workaround)\n * - No native JSONB (uses JSON type)\n * - No array types\n * - Savepoints disabled for safety (MySQL has nested transaction quirks)\n *\n * @example\n * ```typescript\n * const adapter = new MySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * pool: { max: 20 },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport class MySqlAdapter extends DrizzleAdapter {\n // getDrizzle memoization: drizzle v1's constructor builds a relational\n // query builder per table in the relations config (~40 tables), and the\n // service layer resolves an instance on every db access — construct once\n // per relations object (identity-stable: the schema registry caches it\n // and hands out a NEW object on invalidation, which naturally misses\n // this cache and produces a fresh instance).\n private drizzleByRelations = new WeakMap<AnyRelations, unknown>();\n private drizzleBare: unknown;\n\n /**\n * The database dialect - always 'mysql' for this adapter.\n */\n readonly dialect = \"mysql\" as const;\n\n /**\n * Adapter configuration.\n */\n protected readonly config: MySqlAdapterConfig;\n\n /**\n * Connection pool instance.\n */\n private pool: Pool | null = null;\n\n /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */\n private readonly sessions = new Set<Promise<unknown>>();\n\n /**\n * Connection state flag.\n */\n private connected = false;\n\n /**\n * Whether the server accepts `SELECT ... FOR SHARE`, read from its version\n * string at connect. Undefined until then, which callers treat as not known.\n */\n private sharedRowLock: boolean | undefined;\n private functionalIndexes: boolean | undefined;\n\n /**\n * Creates a new MySQL adapter instance.\n *\n * @param config - Adapter configuration\n */\n constructor(config: MySqlAdapterConfig) {\n super();\n this.config = config;\n }\n\n /**\n * Connect to the MySQL database.\n * Creates a connection pool using mysql2.\n *\n * @remarks\n * This method initializes the connection pool and verifies connectivity\n * by executing a simple query. It is idempotent - calling it multiple\n * times will not create multiple pools.\n *\n * @throws {DatabaseError} If connection fails\n */\n async connect(): Promise<void> {\n if (this.connected && this.pool) {\n return;\n }\n\n try {\n // Warned once, here, rather than on every session configuration the\n // standalone connections build: the configuration cannot change\n // between calls.\n this.warnOnDriverZoneDisagreement();\n const poolConfig = this.buildPoolConfig();\n const pool = mysql.createPool(poolConfig);\n // Pin the session time zone the moment each pooled connection opens.\n // Left unpinned, a session serves in whatever zone the server starts it\n // in — `SYSTEM` on the containers, anything on a self-hosted server —\n // and writes, window bounds and session-zone computations all follow\n // it. mysql2 emits `connection` when a physical connection is\n // established and BEFORE it reaches any caller, and each connection\n // queues its commands in order, so this SET has always run by the time\n // the first query handed the connection is sent; a replaced connection\n // is pinned again by the same listener.\n (pool.pool as unknown as PinnablePool).on(\"connection\", connection => {\n connection.query(\"SET SESSION time_zone = '+00:00'\", error => {\n // A session that could not be pinned must never be pooled: it\n // would serve in whatever zone it answers in.\n if (error) connection.destroy();\n });\n });\n // mysql2 queues a request for a connection without limit; it fails as\n // busy past the same bound a new connection gets to open.\n boundPoolWait(\n pool.pool as unknown as QueueingPool,\n poolConfig.connectTimeout ?? 0\n );\n // Cast to our Pool interface - mysql2's mixin types don't resolve properly\n this.pool = pool as unknown as Pool;\n\n // Verify connection with smoke test, then check dialect version.\n // Why: F17 hard-fails at connect on real MySQL <8.0 (no variant\n // token detected). Recognized variants (MariaDB, TiDB, Aurora,\n // PlanetScale, Vitess) log a warning via the adapter logger and\n // proceed. Truly unparseable strings hard-fail so users see the\n // issue at boot rather than mid-apply.\n const connection = await this.pool.getConnection();\n try {\n await connection.query(\"SELECT 1\");\n const { variant, functionalIndexes } = await checkDialectVersion(\n connection,\n \"mysql\",\n {\n // Why: route variant warnings through the adapter's logger so\n // users see a single, consistent log surface.\n onWarning: msg => this.config.logger?.warn?.(msg),\n }\n );\n // MariaDB and TiDB connect through this adapter too, and reject\n // `FOR SHARE`; the capability tells callers which lock they can take.\n this.sharedRowLock = acceptsForShare(variant);\n // Whether a unique index may key on an expression, which per-language uniqueness of a\n // translated value does.\n this.functionalIndexes = functionalIndexes;\n this.connected = true;\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection established\", {\n host: this.config.host ?? \"from URL\",\n database: this.config.database ?? \"from URL\",\n });\n }\n } finally {\n connection.release();\n }\n } catch (error) {\n // Clean up on failure\n if (this.pool) {\n await this.pool.end().catch(() => {});\n this.pool = null;\n }\n throw this.classifyError(error);\n }\n }\n\n /**\n * Disconnect from the MySQL database.\n * Gracefully closes the connection pool.\n *\n * @remarks\n * This method is idempotent - calling it multiple times is safe.\n * It waits for all connections to be released before shutting down,\n * including each running `withSession`'s connection of its own.\n */\n async disconnect(): Promise<void> {\n // Detach the pool FIRST, then drop the memoized drizzle instances —\n // this closes the repopulation window where a concurrent getDrizzle()\n // call during the (async) pool.end() could cache an instance wrapping\n // the closing pool.\n const pool = this.pool;\n this.pool = null;\n this.drizzleBare = undefined;\n this.drizzleByRelations = new WeakMap();\n if (!pool) {\n return;\n }\n\n try {\n // With the pool detached no session starts; those running end first.\n await Promise.allSettled([...this.sessions]);\n await pool.end();\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection closed\");\n }\n } finally {\n this.connected = false;\n }\n }\n\n /**\n * Check if connected to the database.\n */\n isConnected(): boolean {\n return this.connected && this.pool !== null;\n }\n\n /**\n * Get connection pool statistics.\n * Returns null if not connected.\n *\n * @remarks\n * MySQL2 pool exposes different stats than pg:\n * - _allConnections: all connections\n * - _freeConnections: idle connections\n * - _connectionQueue: waiting requests\n */\n getPoolStats(): PoolStats | null {\n if (!this.pool) {\n return null;\n }\n\n // mysql2 Pool internal properties (cast to access internals)\n const poolInternal = this.pool as unknown as {\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n };\n\n const internal = poolInternal.pool;\n if (!internal) {\n return {\n total: 0,\n idle: 0,\n waiting: 0,\n active: 0,\n };\n }\n\n const total = internal._allConnections?.length ?? 0;\n const idle = internal._freeConnections?.length ?? 0;\n const waiting = internal._connectionQueue?.length ?? 0;\n\n return {\n total,\n idle,\n waiting,\n active: total - idle,\n };\n }\n\n /**\n * Execute a raw SQL query.\n *\n * @param sql - SQL query string with ? placeholders\n * @param params - Query parameters\n * @returns Query results\n *\n * @throws {DatabaseError} If query execution fails\n */\n async executeQuery<T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> {\n const pool = this.ensurePool();\n const startTime = Date.now();\n\n try {\n // Outside the calling flow's transaction, if it has one (`notePoolAccess`).\n this.notePoolAccess(sql);\n const [rows] = await pool.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n\n // Log query if logger configured\n if (this.config.logger?.query) {\n const durationMs = Date.now() - startTime;\n this.config.logger.query(sql, params, durationMs);\n }\n\n return rows as T[];\n } catch (error) {\n throw this.classifyError(error, sql);\n }\n }\n\n /**\n * Execute work within a transaction.\n *\n * @param work - Function containing transactional operations\n * @param options - Transaction options (isolation level, timeout, retry)\n * @returns Result of the work function\n *\n * @remarks\n * MySQL transactions support isolation levels. Automatic retry is\n * implemented for deadlocks (error 1213) when `retryCount` is specified.\n *\n * Note: Savepoints are disabled in this adapter for safety due to\n * MySQL's quirks with nested transactions.\n *\n * A call made inside another transaction's work therefore joins it flat,\n * on its connection: its statements are the outer's. Its failure cannot be\n * undone alone, so it fails the whole transaction, even when the outer\n * catches it: the outer's COMMIT is refused, it rolls back, and it rejects\n * with the error that failed the joined call; any later joined call fails\n * with that error at once. A joined call keeps the outer's options and is\n * never retried itself. `retryWhen` is asked first, of every error that\n * ends an attempt, application ones included.\n */\n transaction<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options?: TransactionOptions\n ): Promise<T> {\n return this.openOrJoin(work, options, {\n run: (_attempt, openedAt) => this.runAttempt(work, options, openedAt),\n retryable: error => errnoOf(error) === DEADLOCK,\n describe: error => {\n const errno = errnoOf(error);\n return {\n label: errno === DEADLOCK ? \"deadlock\" : \"a retryable error\",\n detail: { errno },\n };\n },\n logger: this.config.logger,\n });\n }\n\n /**\n * Runs `work` on a connection of its own, opened for it with the pool's\n * connection settings, outside a transaction, each statement committing as\n * it runs.\n *\n * @remarks\n * Not a pooled connection: on a server whose sessions start with autocommit\n * off, one can hold a transaction an earlier caller left open, and turning\n * autocommit on would commit that caller's writes. A new connection holds\n * nothing. It does not count against `pool.max`.\n *\n * Each statement commits as it runs, whatever the server's own autocommit\n * default. With `strictSqlMode` the session runs in strict mode, as a\n * transaction given the same option does, so a statement that would cut a\n * value short fails and changes nothing. The connection is closed\n * afterwards, whether `work` finished or threw, so whatever `work` set on\n * its session (a temporary table, a session variable, a lock) ends with it.\n */\n override async withSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n // A precondition: before any connection is opened.\n this.refuseSessionInTransaction();\n // Connected first, as every other call requires.\n this.ensurePool();\n // Tracked before its connection opens: `disconnect` waits for it.\n const running = this.runSession(work, options);\n this.sessions.add(running);\n try {\n return await running;\n } finally {\n this.sessions.delete(running);\n }\n }\n\n /** `withSession`'s work, on a connection it opens and closes. */\n private async runSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n let connection: OwnConnection;\n try {\n // mysql2's connection already satisfies `OwnConnection`, so it is assigned as it comes.\n connection = await mysql.createConnection(this.buildConnectionConfig());\n } catch (error) {\n throw this.classifyError(error);\n }\n try {\n try {\n // A server whose sessions start with autocommit off would otherwise\n // hold every statement in a transaction nobody commits.\n await connection.query(\"SET SESSION autocommit = 1\");\n // The connection opened outside the pool, so the pool's `connection`\n // listener never pins it; its driver already parses in UTC, and a\n // session still in the server's zone would shift every value a\n // round-trip fails to cancel. The work below never runs unpinned.\n await connection.query(\"SET SESSION time_zone = '+00:00'\");\n if (options?.strictSqlMode) {\n // Nothing to put back: the connection is closed afterwards.\n await this.enterStrictMode(connection, {});\n }\n } catch (error) {\n throw this.classifyError(error);\n }\n return await work({\n execute: async <R = unknown>(\n statement: string,\n params: SqlParam[] = []\n ): Promise<R[]> => {\n const startTime = Date.now();\n try {\n const [rows] = await connection.query<RowDataPacket[]>(\n statement,\n params as unknown[]\n );\n if (this.config.logger?.query) {\n this.config.logger.query(\n statement,\n params,\n Date.now() - startTime\n );\n }\n return rows as R[];\n } catch (error) {\n throw this.classifyError(error, statement);\n }\n },\n });\n } finally {\n connection.destroy();\n }\n }\n\n /** One attempt at an outermost transaction, on a connection of its own. */\n private async runAttempt<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options: TransactionOptions | undefined,\n openedAt: string | undefined\n ) {\n const pool = this.ensurePool();\n // A checkout, taken from the pool like `executeQuery`'s queries are.\n this.notePoolAccess();\n const connection = await pool.getConnection();\n // Set when the ROLLBACK failed (`returnTransactionConnection`).\n let rollbackFailed = false;\n // What `beginTransaction` changed on the session, to put back once the\n // transaction has ended. Filled in as each change lands, so a setup that\n // fails part way still puts back what it had already changed.\n const restore: NonNullable<SessionRestore> = {};\n const settled = await this.settleOutermost(\n {\n context: this.createTransactionContext(connection),\n join: this.joinSteps(connection),\n openedAt,\n begin: () => this.beginTransaction(connection, options, restore),\n commit: async () => {\n await connection.query(\"COMMIT\");\n },\n // A failure is recorded for the return and rethrown, so the base\n // knows the ROLLBACK is unconfirmed.\n rollback: async () => {\n await connection.query(\"ROLLBACK\").catch((rollbackError: unknown) => {\n rollbackFailed = true;\n throw rollbackError;\n });\n },\n },\n work,\n options\n );\n // Returned before anything held runs, so an effect that opens a\n // transaction takes one connection, not two.\n await returnTransactionConnection(connection, rollbackFailed, restore);\n return settled;\n }\n\n /**\n * How a call made inside a transaction on `connection` joins it: flat, on\n * the same connection, with nothing to send. Any failure inside it fails\n * the whole transaction, since no savepoint can undo the part alone.\n */\n private joinSteps(connection: PoolConnection) {\n const callbackConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n return this.joinStepsOn(\n () => this.createTransactionContext(connection),\n relations =>\n relations\n ? drizzle({ client: callbackConnection, relations })\n : drizzle({ client: callbackConnection }),\n context => ({\n context,\n begin() {},\n commit() {},\n rollback() {},\n dooms: () => true,\n })\n );\n }\n\n /**\n * Get MySQL database capabilities.\n *\n * @remarks\n * MySQL has some limitations:\n * - No JSONB (uses JSON)\n * - No arrays\n * - No native ILIKE\n * - No RETURNING clause\n * - Savepoints disabled for safety\n */\n getCapabilities(): DatabaseCapabilities {\n return {\n dialect: \"mysql\",\n supportsJsonb: false, // MySQL uses JSON, not JSONB\n supportsJson: true,\n supportsArrays: false, // MySQL doesn't support array types\n supportsGeneratedColumns: true, // MySQL 5.7.6+\n supportsFts: true, // MySQL has FULLTEXT indexes\n supportsIlike: false, // No native ILIKE, use LOWER() LIKE\n supportsReturning: false, // No RETURNING clause in MySQL\n supportsSavepoints: false, // Disabled for safety per approved approach\n supportsOnConflict: true, // ON DUPLICATE KEY UPDATE\n maxParamsPerQuery: 65535, // MySQL limit\n maxIdentifierLength: 64, // MySQL limit\n sharedRowLock: this.sharedRowLock, // Known once connected\n functionalIndexes: this.functionalIndexes, // Known once connected\n };\n }\n\n /**\n * Build a placeholder for MySQL (uses ? instead of $1, $2, etc.)\n *\n * @param _index - Parameter index (ignored for MySQL)\n * @returns The ? placeholder\n */\n protected buildPlaceholder(_index: number): string {\n return \"?\";\n }\n\n /**\n * Build multiple placeholders for MySQL.\n *\n * @param count - Number of placeholders needed\n * @param _startIndex - Starting index (ignored for MySQL)\n * @returns Comma-separated ? placeholders\n */\n protected buildPlaceholders(count: number, _startIndex: number = 0): string {\n return Array(count).fill(\"?\").join(\", \");\n }\n\n /**\n * Escape an identifier for MySQL (uses backticks instead of double quotes).\n *\n * @param identifier - The identifier to escape\n * @returns Escaped identifier with backticks\n */\n protected escapeIdentifier(identifier: string): string {\n // MySQL uses backticks for identifiers\n return `\\`${identifier.replace(/`/g, \"``\")}\\``;\n }\n\n // ============================================================\n // Protected Helper Methods\n // ============================================================\n\n /**\n * Ensures pool is connected and returns it.\n *\n * @throws {DatabaseError} If not connected\n */\n private ensurePool(): Pool {\n if (!this.pool) {\n throw createDatabaseError({\n kind: \"connection\",\n message: \"MySqlAdapter is not connected. Call connect() first.\",\n });\n }\n return this.pool;\n }\n\n /**\n * Return the typed Drizzle instance for MySQL.\n * Guarded for server-only usage and requires an active connection.\n *\n * @param schema - Optional schema for relational queries (db.query.*)\n * @returns Drizzle ORM instance wrapping the mysql2 pool connection\n * @throws {Error} If called in browser or not connected\n */\n getDrizzle<T = MySql2Database<AnyRelations>>(relations?: AnyRelations): T {\n if (typeof window !== \"undefined\") {\n throw new Error(\"getDrizzle() is server-only\");\n }\n const pool = this.ensurePool();\n if (!relations) {\n this.drizzleBare ??= this.poolDrizzle(pool);\n return this.drizzleBare as T;\n }\n let cached = this.drizzleByRelations.get(relations);\n if (!cached) {\n cached = this.poolDrizzle(pool, relations);\n this.drizzleByRelations.set(relations, cached);\n }\n return cached as T;\n }\n\n /**\n * A Drizzle handle on the pool, for `getDrizzle`.\n *\n * Its `transaction` runs on a connection this adapter checks out and\n * returns itself (`pinDrizzleTransactions`): destroyed rather than pooled\n * when no ROLLBACK could be confirmed on it. And it counts for\n * `transactionOpenInFlow`, as the adapter's does.\n */\n private poolDrizzle(pool: Pool, relations?: AnyRelations) {\n // drizzle v1's mysql2 driver takes the CALLBACK pool and connection,\n // which the mysql2/promise wrappers expose on `.pool` and `.connection`:\n // handed a wrapper, it throws (\"Cannot set properties of undefined\n // (setting 'supportBigNumbers')\"). The pre-v1 `mode` option is gone.\n const onClient = (client: CallbackPool | CallbackConnection) =>\n relations ? drizzle({ client, relations }) : drizzle({ client });\n // The handle's own queries go to the pool, so it is handed a view of the\n // pool that reports them (`observedPool`).\n const callbackPool = this.observedPool(\n (pool as unknown as { pool: CallbackPool }).pool,\n [\"query\", \"execute\", \"getConnection\"]\n );\n // Its transactions join the one the calling flow is inside, as the\n // adapter's `transaction()` does, and open one of this adapter's\n // otherwise (`joiningHandle`).\n return this.joiningHandle(\n onClient(callbackPool),\n async () => {\n const connection = await pool.getConnection();\n return {\n drizzle: onClient(\n (connection as unknown as { connection: CallbackConnection })\n .connection\n ),\n rollback: () => connection.query(\"ROLLBACK\"),\n release: dirty => {\n if (dirty) connection.destroy();\n else connection.release();\n },\n join: this.joinSteps(connection),\n };\n },\n relations\n );\n }\n\n /**\n * Warns when a caller's configured driver zone does not agree with the\n * pinned UTC session.\n *\n * mysql2's driver-level `timezone` decides how it parses the date strings\n * the pinned session answers with and how it binds raw Date parameters, so\n * a non-UTC spelling there would shift values a round-trip fails to\n * cancel. The configuration is aligned to the pin (`buildConnectionConfig`\n * sets UTC unconditionally, for pooled and standalone connections alike),\n * never rewritten silently: the caller configured a reading, and this is\n * where that reading is named as lost. Runs from `connect`, once per pool\n * build — the configuration cannot change between calls.\n */\n private warnOnDriverZoneDisagreement(): void {\n // mysql2's own default is `local`, so an unset configuration is judged\n // the same way: it agrees only on a UTC host.\n const requestedZone = this.config.timezone ?? \"local\";\n if (isUtcDriverZone(requestedZone)) return;\n this.config.logger?.warn?.(\n `Driver timezone \"${requestedZone}\" is not UTC; it is aligned to the pinned UTC session and no longer shifts reads or writes.`\n );\n }\n\n /**\n * Builds mysql2 Pool configuration from adapter config.\n */\n private buildPoolConfig(): PoolOptions {\n const config: PoolOptions = this.buildConnectionConfig();\n\n // Pool settings - mysql2 uses different property names\n config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;\n config.idleTimeout =\n this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;\n\n // A request waits for a connection when the pool is full. The queue has\n // no length limit; each wait in it is bounded instead (`boundPoolWait`).\n config.waitForConnections = true;\n config.queueLimit = 0;\n\n return config;\n }\n\n /**\n * Builds the settings every connection opens with, pooled or not, from\n * adapter config.\n */\n private buildConnectionConfig(): ConnectionOptions {\n const config: ConnectionOptions = this.buildAddressConfig();\n\n config.connectTimeout =\n this.config.pool?.connectionTimeoutMs ??\n DEFAULT_POOL_CONFIG.connectionTimeoutMs;\n\n // SSL configuration\n if (this.config.ssl) {\n config.ssl = this.buildSslConfig(this.config.ssl);\n }\n\n // MySQL-specific options. Every connection this adapter opens serves a\n // UTC session (see the pins in `connect` and `runSession`), so the\n // driver zone is pinned with them: mysql2 parses the date strings a\n // pinned session answers with and binds raw Date parameters, and a\n // driver in one zone beside a session in another shifts values every\n // time a round-trip fails to cancel. A configured zone that is not UTC\n // is aligned rather than refused — the pin is the deliberate boundary —\n // and `connect` warns the caller, once, that the shift they configured\n // is gone.\n config.timezone = \"Z\";\n\n if (this.config.charset) {\n config.charset = this.config.charset;\n }\n\n // Enable multiple statements if needed (disabled by default for security)\n config.multipleStatements = false;\n\n // Date handling\n config.dateStrings = false; // Return Date objects\n\n return config;\n }\n\n /** Where to connect: the connection string, or the explicit options. */\n private buildAddressConfig(): ConnectionOptions {\n if (this.config.url) return { uri: this.config.url };\n const { host, port, database, user, password } = this.config;\n const config: ConnectionOptions = { host, port, database, user, password };\n // An option left unset is left out, for mysql2's own default.\n for (const key of Object.keys(config) as (keyof ConnectionOptions)[]) {\n if (!config[key]) delete config[key];\n }\n return config;\n }\n\n /** mysql2's `ssl` option for the adapter's `ssl` setting. */\n private buildSslConfig(\n ssl: NonNullable<MySqlAdapterConfig[\"ssl\"]>\n ): ConnectionOptions[\"ssl\"] {\n if (typeof ssl === \"boolean\") {\n return ssl ? {} : undefined;\n }\n return {\n rejectUnauthorized: ssl.rejectUnauthorized,\n ca: ssl.ca,\n cert: ssl.cert,\n key: ssl.key,\n };\n }\n\n /**\n * Begins a transaction with the specified options, recording in `changed`\n * each session setting it changes, as it changes it, for `restoreSession`\n * to put back.\n */\n private async beginTransaction(\n connection: PoolConnection,\n options: TransactionOptions | undefined,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n // Set isolation level if specified (must be done before BEGIN)\n if (options?.isolationLevel) {\n const isolationMap: Record<string, string> = {\n \"read uncommitted\": \"READ UNCOMMITTED\",\n \"read committed\": \"READ COMMITTED\",\n \"repeatable read\": \"REPEATABLE READ\",\n serializable: \"SERIALIZABLE\",\n };\n const level = isolationMap[options.isolationLevel];\n if (level) {\n await connection.query(`SET TRANSACTION ISOLATION LEVEL ${level}`);\n }\n }\n\n // Set read-only mode if specified\n if (options?.readOnly) {\n await connection.query(\"SET TRANSACTION READ ONLY\");\n }\n\n // Begin the transaction\n await connection.query(\"START TRANSACTION\");\n\n // Set lock wait timeout if specified\n if (options?.timeoutMs) {\n // Read first, so the session's own value can be put back once the\n // transaction has ended (`SessionRestore`).\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout\"\n );\n const previous = Number(rows[0]?.timeout);\n // MySQL uses seconds for lock_wait_timeout\n const timeoutSeconds = Math.ceil(options.timeoutMs / 1000);\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`\n );\n changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;\n }\n\n if (options?.strictSqlMode) {\n await this.enterStrictMode(connection, changed);\n }\n }\n\n /**\n * Strict mode for the rest of this connection's session: an INSERT, UPDATE\n * or ALTER that would cut a string to its column's length, or clamp a\n * number to its range, fails instead of applying with a warning. Added to\n * the session's own mode, so every other flag it had still holds, and\n * recorded in `changed` for `restoreSession` to put back.\n */\n private async enterStrictMode(\n connection: Queryable,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.sql_mode AS mode\"\n );\n const previous: unknown = rows[0]?.mode;\n // Recorded before the SET: one the server applied but whose reply was\n // lost still has its mode put back.\n changed.sqlMode = typeof previous === \"string\" ? previous : null;\n await connection.query(\n \"SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))\"\n );\n }\n\n /**\n * Creates a TransactionContext for the given connection.\n *\n * @remarks\n * Note: Savepoint methods are not implemented (set to undefined)\n * as savepoints are disabled in this adapter per approved approach.\n */\n /**\n * Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL\n * and SQLite, on the tables where MySQL can keep that promise.\n *\n * MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`\n * fires on a collision with ANY unique key, and a failed insert does not\n * say reliably which one. So the conflict target must be the table's only\n * unique key apart from its primary key, and any other table is refused\n * before anything is written. On such a table the row is inserted first —\n * its validation and triggers apply, as they do before `ON CONFLICT`\n * elsewhere — and a duplicate is resolved by a locking read of the target\n * key, which sees the latest committed row rather than the transaction's\n * snapshot. The row found is kept as it is when there is nothing to update\n * (a no-op assignment would still run its update triggers), or updated by\n * its key. No row there means the collision was on the primary key, and it\n * is refused as `ON CONFLICT (target)` refuses it.\n *\n * The steps run in the caller's transaction, or in one of their own, since a\n * locking read locks nothing past its statement under autocommit. Rows are\n * read back by their primary key, so a trigger that rewrites the conflict\n * value still returns the row stored. The conflict columns must also be\n * given values: MySQL cannot look a row up by a key the database would only\n * fill in itself. A conflict is resolved on the caller's key value: a\n * BEFORE INSERT trigger that moves the key onto another row's value is\n * refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a\n * caller's transaction stays usable.\n */\n override async upsert<T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions,\n executor?: unknown\n ): Promise<T> {\n const tableObj = this.getTableObject(table) as MySqlTable | undefined;\n // Unknown table: the base method reports it as every CRUD method does.\n if (!tableObj) return super.upsert<T>(table, data, options, executor);\n let target: { where: SQL | undefined; nullKey: boolean };\n try {\n // Judged before anything else, so a refused call takes no connection.\n target = this.upsertTarget(tableObj, table, data, options);\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n // A locking read holds its lock only inside a transaction: under autocommit\n // it is released when its own statement ends, and the next statement may\n // run on another pooled connection. Without the caller's transaction, the\n // insert, the read and the update get one of their own, on one connection.\n if (executor === undefined) {\n return this.transaction(tx => tx.upsert<T>(table, data, options));\n }\n try {\n const db = executor as MySql2Database<AnyRelations>;\n // A conflict key holding NULL never conflicts: a unique index admits any\n // number of NULLs. It is a plain insert, read back by its primary key.\n if (target.nullKey) {\n return ((await this.insertRow(db, tableObj, data)) ?? data) as T;\n }\n return (await this.insertOrResolve(\n db,\n tableObj,\n data,\n options,\n target.where\n )) as T;\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n }\n\n /**\n * What an upsert is keyed on: the target's condition, and whether its key\n * holds a NULL. Refuses a target this dialect cannot keep to, and a conflict\n * column left for the database to fill — MySQL cannot look a row up by a key\n * value only the database would supply.\n */\n private upsertTarget(\n tableObj: MySqlTable,\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions\n ): { where: SQL | undefined; nullKey: boolean } {\n // MySQL needs every conflict value whether or not it updates: the row is\n // found by them after a duplicate, so the keep-existing rule applies to\n // every call here.\n this.assertUpsertTarget(table, data, options, true);\n const key = this.conflictKey(tableObj, table, options.conflictColumns);\n const values = options.conflictColumns.map(column => data[column]);\n return {\n where: and(...key.map((column, i) => eq(column, values[i] as never))),\n nullKey: values.some(value => value === null),\n };\n }\n\n /**\n * The table's columns for a conflict target, refusing a target MySQL cannot\n * keep to: one that is not a primary or unique key, which an update could\n * spread over several rows, and one beside which the table has another\n * unique key, a collision on which MySQL would not tell apart.\n */\n private conflictKey(\n tableObj: MySqlTable,\n table: string,\n conflictColumns: readonly string[]\n ): Column[] {\n const record = tableObj as unknown as Record<string, Column | undefined>;\n const columns = conflictColumns.map(name => record[name]);\n const names = new Set(columns.map(column => column?.name));\n const isTarget = (key: readonly string[]) =>\n key.length === names.size && key.every(name => names.has(name));\n const { primary, unique } = keysOf(tableObj);\n // Every row this upsert returns is read back by its primary key, so a\n // table without one has no stable way to find the row it wrote.\n if (primary.length === 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`,\n });\n }\n const known = columns.every(column => column !== undefined);\n if (\n !known ||\n !(isTarget(primary) || unique.some(k => isTarget(k.columns)))\n ) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: the conflict columns (${conflictColumns.join(\", \")}) are not a primary or unique key of the table.`,\n });\n }\n const others = unique.filter(key => !isTarget(key.columns));\n if (others.length > 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL cannot limit a duplicate to the conflict columns (${conflictColumns.join(\", \")}) while the table has other unique keys (${others.map(key => key.name).join(\", \")}). Upsert only on a table whose one unique key besides the primary key is the conflict target, or insert and update inside a transaction.`,\n });\n }\n return columns;\n }\n\n /** The update an upsert applies to an existing row: never its key. */\n private updateValues(\n data: Record<string, unknown>,\n options: UpsertOptions\n ): Record<string, unknown> {\n const key = new Set(options.conflictColumns);\n const update: Record<string, unknown> = {};\n for (const column of options.updateColumns ?? Object.keys(data)) {\n if (!key.has(column) && column in data) update[column] = data[column];\n }\n return update;\n }\n\n /** The row matching `where`, by a locking (current) read. */\n private async lockedRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n where: SQL | undefined\n ): Promise<unknown> {\n const rows = await db\n .select()\n .from(tableObj)\n .where(where)\n .limit(1)\n .for(\"update\");\n return rows[0];\n }\n\n /**\n * Insert the row, or resolve a duplicate on the target key. The insert is\n * read back by the primary key it reports, not by the conflict key: a\n * BEFORE INSERT trigger may have rewritten the conflict value, and the row\n * stored is the one to return.\n *\n * A duplicate is resolved when a row holds the TARGET key, whichever unique\n * key MySQL names in its error. PostgreSQL checks the target's index before\n * any other, so a row colliding on the target and on another key resolves\n * on the target there too; refusing it here because MySQL happened to check\n * the other index first would answer that case differently. A target row\n * committed by another transaction after this insert failed is the order in\n * which that transaction ran first, where PostgreSQL also resolves on it.\n */\n private async insertOrResolve(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>,\n options: UpsertOptions,\n where: SQL | undefined\n ): Promise<unknown> {\n let failure: unknown;\n try {\n const stored = await this.insertRow(db, tableObj, data);\n return stored ?? (await this.lockedRow(db, tableObj, where));\n } catch (error) {\n // Classified as everything else is, through the driver's own error\n // under Drizzle's wrapper (`classifyError`).\n if (this.classifyError(error).kind !== \"unique_violation\") throw error;\n failure = error;\n }\n const existing = await this.lockedRow(db, tableObj, where);\n // No row holds the target key: the collision was on another unique key,\n // refused as `ON CONFLICT (target)` refuses it, as the unique violation the\n // classifier reads it to be.\n if (existing === undefined) throw failure;\n const update = this.updateValues(data, options);\n if (Object.keys(update).length === 0) return existing;\n await db.update(tableObj).set(update).where(where);\n // Read back by the row's primary key as the update left it: the update may\n // set the key itself (every given column is updated by default, as `ON\n // CONFLICT DO UPDATE` does elsewhere), and a BEFORE UPDATE trigger may\n // have changed the conflict value the update was keyed on. A trigger that\n // changed the primary key instead leaves the conflict value standing, so\n // the row is looked for there next; one that changed both is not found.\n const identity = this.byIdentity(tableObj, {\n ...(existing as Record<string, unknown>),\n ...update,\n });\n const updated =\n identity === undefined\n ? undefined\n : await this.lockedRow(db, tableObj, identity);\n return updated ?? this.lockedRow(db, tableObj, where);\n }\n\n /**\n * Insert a row and read it back by its primary key: the value the caller\n * gave, or the one MySQL or the model generated, which `$returningId()`\n * reports and a given value it does not. Undefined when the table has no\n * primary key to read by.\n */\n private async insertRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>\n ): Promise<unknown> {\n const [generated] = (await db\n .insert(tableObj)\n .values(data as never)\n .$returningId()) as Record<string, unknown>[];\n // A key the caller gave wins; one given as undefined or null does not hide\n // the value MySQL generated for it.\n const given = Object.fromEntries(\n Object.entries(data).filter(\n ([, value]) => value !== undefined && value !== null\n )\n );\n const identity = this.byIdentity(tableObj, { ...generated, ...given });\n return identity === undefined\n ? undefined\n : this.lockedRow(db, tableObj, identity);\n }\n\n /**\n * A row's primary key as a condition, from a record keyed as the model's\n * properties are. Undefined when the table has no primary key or the record\n * lacks part of it.\n */\n private byIdentity(\n tableObj: MySqlTable,\n record: Record<string, unknown>\n ): SQL | undefined {\n const primary = new Set(keysOf(tableObj).primary);\n const parts = Object.entries(getColumns(tableObj)).filter(([, column]) =>\n primary.has(column.name)\n );\n if (\n parts.length === 0 ||\n parts.some(\n ([name]) => record[name] === undefined || record[name] === null\n )\n ) {\n return undefined;\n }\n return and(\n ...parts.map(([name, column]) => eq(column, record[name] as never))\n );\n }\n\n /** A table-level `primaryKey({ columns })`, read through the MySQL table config. */\n protected override compositePrimaryKey(\n tableObj: Record<string, unknown>\n ): object[] {\n return getTableConfig(\n tableObj as unknown as MySqlTable\n ).primaryKeys.flatMap(key => key.columns);\n }\n\n private createTransactionContext(\n connection: PoolConnection\n ): TransactionContext {\n // Bind a Drizzle instance to this transaction's checked-out connection so\n // the delegated CRUD methods run inside the transaction and see its\n // uncommitted rows. drizzle's mysql2 driver needs the underlying CALLBACK\n // connection, which the mysql2/promise wrapper exposes on `.connection`\n // (mirrors the `.pool` unwrap in getDrizzle()); getDrizzle() itself wraps\n // the pool, which would use a different connection. Built lazily and\n // memoized: transactions that use only raw execute/insert never construct\n // it.\n const txConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n const txHandles = this.transactionDrizzleHandles(relations =>\n relations\n ? drizzle({ client: txConnection, relations })\n : drizzle({ client: txConnection })\n );\n const txDb = txHandles.bare;\n return {\n drizzleWithRelations: txHandles.withRelations,\n\n execute: async <T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> => {\n const [rows] = await connection.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n return rows as T[];\n },\n\n // Run on the transaction-bound Drizzle instance rather than the pool, so\n // the statement is part of this transaction and sees its uncommitted rows.\n runStatement: async (statement: SQL): Promise<void> => {\n await txDb().execute(statement);\n },\n\n // mysql2 answers a `[rows, fields]` tuple; the transaction-bound instance\n // keeps the read inside this transaction so it sees its uncommitted writes.\n queryStatement: async <T = Record<string, unknown>>(\n statement: SQL\n ): Promise<T[]> => {\n const result = await txDb().execute(statement);\n // A tuple's first element is the rows. Anything else was not understood,\n // and must not reach a caller as \"there is nothing there\" — the same\n // reason the pooled `queryStatement` refuses rather than answering\n // empty.\n if (!Array.isArray(result)) {\n throw this.createDatabaseError(\n \"query\",\n \"Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.\",\n undefined\n );\n }\n return result[0] as unknown as T[];\n },\n\n lockRow: async (table: string, id: SqlParam): Promise<void> => {\n const idColumn = this.escapeIdentifier(\"id\");\n await connection.query(\n `SELECT ${idColumn} FROM ${this.escapeIdentifier(table)} ` +\n `WHERE ${idColumn} = ? FOR UPDATE`,\n [id] as unknown[]\n );\n },\n\n insert: async <T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options?: InsertOptions\n ): Promise<T> => {\n // Refused before any statement, as the pooled insert is: a table this\n // instance never registered may not match what its code would write.\n const insertTableObj = this.resolvedTableObject(table);\n const mapped = this.mapRowToRawSql(insertTableObj, data);\n const columns = Object.keys(mapped);\n const values = Object.values(mapped);\n const placeholders = this.buildPlaceholders(values.length, 0);\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES (${placeholders})`;\n\n const [result] = await connection.query<ResultSetHeader>(sql, values);\n\n const ret = options?.returning;\n // No columns requested: skip the select-back reread entirely.\n if (Array.isArray(ret) && ret.length === 0) {\n return undefined as T;\n }\n\n // MySQL has no RETURNING; select the inserted row back. Project only the\n // requested columns so a large JSON snapshot is not read unless asked.\n // Each timestamp's wall clock, spelled out by the database. mysql2\n // turns one into a `Date` in the LOCAL zone before this code sees it,\n // and that conversion cannot be undone: a wall clock inside a\n // daylight-saving gap is normalized away.\n const aliases = this.dateWallClockAliases(insertTableObj, ret ?? \"*\");\n const spelled = aliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const projected =\n !ret || ret === \"*\"\n ? \"*\"\n : this.mapColumnNamesToSql(insertTableObj, ret)\n .map(c => this.escapeIdentifier(c))\n .join(\", \");\n const selectList = spelled ? `${projected}, ${spelled}` : projected;\n\n // Prefer the primary key: auto-increment via insertId, otherwise a\n // supplied id (manually-keyed tables like nextly_versions). Matching by\n // all values is a last resort because `col = NULL` never matches, so a\n // row with nullable columns would not be found by that path.\n const idValue = result.insertId ? result.insertId : mapped.id;\n if (idValue !== undefined) {\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE id = ?`,\n [idValue]\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n }\n\n const whereClauses = columns.map(\n c => `${this.escapeIdentifier(c)} = ?`\n );\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE ${whereClauses.join(\" AND \")} LIMIT 1`,\n values\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n },\n\n insertMany: async <T = unknown>(\n table: string,\n data: Record<string, unknown>[],\n options?: InsertOptions\n ): Promise<T[]> => {\n if (data.length === 0) return [];\n const retMany = options?.returning;\n const skipReread = Array.isArray(retMany) && retMany.length === 0;\n\n // Refused before any statement, as `insert` above is.\n const tableObj = this.resolvedTableObject(table);\n const mappedRecords = data.map(r => this.mapRowToRawSql(tableObj, r));\n const columns = Object.keys(mappedRecords[0]);\n const allValues: unknown[] = [];\n const valuesClauses: string[] = [];\n\n for (const record of mappedRecords) {\n const placeholders: string[] = [];\n for (const col of columns) {\n allValues.push(record[col]);\n placeholders.push(\"?\");\n }\n valuesClauses.push(`(${placeholders.join(\", \")})`);\n }\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES ${valuesClauses.join(\", \")}`;\n\n const [result] = await connection.query<ResultSetHeader>(\n sql,\n allValues\n );\n\n // For bulk insert, we need to SELECT the inserted rows\n // MySQL's insertId gives the first auto-increment ID\n if (!skipReread && result.insertId && result.affectedRows > 0) {\n const ids: number[] = [];\n for (let i = 0; i < result.affectedRows; i++) {\n ids.push(result.insertId + i);\n }\n const placeholders = ids.map(() => \"?\").join(\", \");\n const bulkAliases = this.dateWallClockAliases(tableObj, \"*\");\n const bulkSpelled = bulkAliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT *${bulkSpelled ? `, ${bulkSpelled}` : \"\"} FROM ${this.escapeIdentifier(table)} WHERE id IN (${placeholders})`,\n ids\n );\n return (rows as T[]).map(r =>\n this.mapRowFromRawSql(tableObj, r, bulkAliases)\n );\n }\n\n // Fallback: return empty if we can't determine inserted rows\n return [];\n },\n\n // Adapter-built, as `insert` above is; `transactionUpdate` says why.\n // A column the model does not declare binds natively, as every value\n // on this path's insert does.\n update: this.transactionUpdate(\n txDb,\n async statement => {\n // No RETURNING on MySQL: nothing to report, the read-back is by\n // the caller's predicate.\n await txDb().execute(statement);\n return undefined;\n },\n // Structured values as JSON text: mysql2's formatter spells an object\n // as `key = value` pairs and an array as a value list, neither JSON.\n value => this.bindUnmodeledStructuredAsJson(value)\n ),\n\n ...this.createTransactionForwarders(txDb),\n\n // Savepoints disabled per approved approach\n savepoint: undefined,\n rollbackToSavepoint: undefined,\n releaseSavepoint: undefined,\n };\n }\n\n /**\n * Classifies a MySQL error into a DatabaseError.\n *\n * @param error - Original error from mysql2\n * @param sql - SQL statement that caused the error (optional)\n * @returns DatabaseError with proper classification\n */\n protected override classifyError(\n error: unknown,\n sql?: string\n ): DatabaseError {\n // Why short-circuit on existing DatabaseError: F17's\n // UnsupportedDialectVersionError is already a typed DatabaseError with\n // kind: \"unsupported_version\" plus detectedVersion/requiredVersion\n // fields. Re-wrapping it here would erase those fields and re-tag it\n // as kind: \"unknown\".\n if (isDatabaseError(error)) return error;\n\n // Classified from mysql2's own error, which Drizzle puts on `cause` when it\n // wraps a failed query (`driverErrorIn`); it is the one carrying `errno`.\n const mysqlError = this.driverErrorIn<{\n errno?: number;\n code?: string;\n sqlState?: string;\n message?: string;\n sql?: string;\n cause?: unknown;\n }>(error, candidate => typeof candidate.errno === \"number\");\n\n // Determine error kind from MySQL error number\n const kind: DatabaseErrorKind =\n (mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno]) || \"unknown\";\n\n // Build error message\n let message = mysqlError.message ?? String(error);\n if (sql && kind === \"query\") {\n message = `Query failed: ${message}`;\n }\n\n return createDatabaseError({\n kind,\n message,\n code: mysqlError.code ?? mysqlError.errno?.toString(),\n detail: mysqlError.sql,\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\n/** A table's keys by database column name, as its model declares them. */\ninterface TableKeys {\n primary: string[];\n unique: { name: string; columns: string[] }[];\n}\n\nconst TABLE_KEYS = new WeakMap<MySqlTable, TableKeys>();\n\n/**\n * A table's primary key and its unique keys other than the primary key, read\n * from its model once and kept for the table's lifetime: a model is not\n * changed in place, a new schema brings new table objects.\n */\nfunction keysOf(tableObj: MySqlTable): TableKeys {\n const cached = TABLE_KEYS.get(tableObj);\n if (cached) return cached;\n const config = getTableConfig(tableObj);\n const primary = [\n ...config.columns.filter(column => column.primary),\n ...config.primaryKeys.flatMap(key => key.columns),\n ].map(column => column.name);\n const unique = [\n ...config.columns\n .filter(column => column.isUnique && !column.primary)\n .map(column => ({\n // Drizzle's own name for an unnamed column-level unique key.\n name: column.uniqueName ?? `${config.name}_${column.name}_unique`,\n columns: [column.name],\n })),\n ...config.uniqueConstraints.map(constraint => ({\n name: constraint.name,\n columns: constraint.columns.map(column => column.name),\n })),\n ...config.indexes\n .filter(index => index.config.unique === true)\n .map(index => ({\n name: index.config.name,\n columns: (index.config.columns as { name: string }[]).map(\n column => column.name\n ),\n })),\n ];\n const keys = { primary, unique };\n TABLE_KEYS.set(tableObj, keys);\n return keys;\n}\n\n/**\n * Create a MySQL database adapter.\n *\n * @param config - MySQL adapter configuration\n * @returns A new MySqlAdapter instance\n *\n * @example\n * ```typescript\n * // Simple usage with URL\n * const adapter = createMySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * });\n *\n * // Full configuration\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * pool: {\n * min: 2,\n * max: 20,\n * idleTimeoutMs: 30000,\n * connectionTimeoutMs: 10000,\n * },\n * ssl: {\n * rejectUnauthorized: true,\n * },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport function createMySqlAdapter(config: MySqlAdapterConfig): MySqlAdapter {\n return new MySqlAdapter(config);\n}\n\n/**\n * Type guard to check if a value is a MySqlAdapter.\n *\n * @param value - Value to check\n * @returns True if value is a MySqlAdapter instance\n *\n * @example\n * ```typescript\n * if (isMySqlAdapter(adapter)) {\n * // TypeScript knows adapter is MySqlAdapter\n * console.log('Using MySQL');\n * }\n * ```\n */\nexport function isMySqlAdapter(value: unknown): value is MySqlAdapter {\n return value instanceof MySqlAdapter;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["poolWaitTimeoutError","DrizzleAdapter","mysql","checkDialectVersion","acceptsForShare","drizzle","createDatabaseError","and","eq","getColumns","getTableConfig","isDatabaseError"],"sources":["../src/pool-wait.ts","../src/index.ts"],"sourcesContent":["/**\n * The bounded wait for a pooled connection.\n *\n * mysql2 has no such bound. A request that finds every connection in use is\n * queued until one is released, however long that takes, so a database that\n * stays busy holds every request behind it indefinitely. This bounds the wait\n * the way pg-pool's `connectionTimeoutMillis` does: past the bound, the\n * request fails with the adapters' own pool-wait timeout error, which core\n * answers with \"database busy\".\n *\n * Only the wait in the queue: a request that opens a new connection is bounded\n * by mysql2's `connectTimeout` instead, set from the same setting, and a\n * connection that cannot be opened is a database that cannot be reached\n * rather than one that is busy.\n */\nimport { poolWaitTimeoutError } from \"@nextlyhq/adapter-drizzle/types\";\n\n/** A connection the pool hands out, as far as the bound needs one. */\ninterface QueuedConnection {\n release(): void;\n}\n\ntype GetConnectionCallback = (\n err: Error | null,\n connection?: QueuedConnection\n) => void;\n\n/**\n * The part of mysql2's callback pool the bound uses. Declared here because\n * mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\nexport interface QueueingPool {\n getConnection(callback: GetConnectionCallback): void;\n once(event: \"enqueue\", listener: () => void): unknown;\n removeListener(event: \"enqueue\", listener: () => void): unknown;\n /** mysql2's queue of waiting requests (a Denque). */\n _connectionQueue?: {\n length: number;\n peekAt(index: number): unknown;\n removeOne(index: number): unknown;\n };\n}\n\n/**\n * Takes a request that gave up out of the pool's queue, so a pool that stays\n * saturated does not collect every request that has already failed, and a\n * released connection goes to one still waiting. Returns whether it was\n * there: with diagnostics tracing on, mysql2 queues a wrapper of the\n * callback rather than the callback itself, and then it stays queued until a\n * connection reaches it.\n */\nfunction leaveQueue(pool: QueueingPool, waiting: GetConnectionCallback) {\n const queue = pool._connectionQueue;\n if (!queue) return false;\n for (let at = 0; at < queue.length; at++) {\n if (queue.peekAt(at) === waiting) {\n queue.removeOne(at);\n return true;\n }\n }\n return false;\n}\n\n/**\n * Makes `pool` fail a request that has waited `boundMs` in its queue for a\n * connection, and returns it. A bound of 0 leaves the wait unbounded, as\n * pg-pool reads a `connectionTimeoutMillis` of 0.\n *\n * Replaces the pool's own `getConnection`, which its `query` and `execute`\n * go through, so every path to a connection is bounded: the adapter's\n * statements, its transactions, and the Drizzle handle core services query\n * through.\n *\n * A request that gives up leaves the queue. Where mysql2 cannot be asked to\n * drop it, the connection it is eventually handed is released straight back\n * rather than held by a request that has already failed.\n */\nexport function boundPoolWait<P extends QueueingPool>(\n pool: P,\n boundMs: number\n): P {\n if (boundMs <= 0) return pool;\n const getConnection = pool.getConnection.bind(pool);\n const bounded = (callback: GetConnectionCallback) => {\n let answered = false;\n // Set only once the request has queued; read by the answer that ends it.\n const wait: { timer?: ReturnType<typeof setTimeout> } = {};\n // mysql2 emits `enqueue` synchronously, inside this call, exactly when\n // the request joins the queue rather than taking or opening a connection.\n let queued = false;\n const onEnqueue = () => (queued = true);\n pool.once(\"enqueue\", onEnqueue);\n const waiting: GetConnectionCallback = (err, connection) => {\n if (answered) {\n connection?.release();\n return;\n }\n answered = true;\n clearTimeout(wait.timer);\n callback(err, connection);\n };\n getConnection(waiting);\n pool.removeListener(\"enqueue\", onEnqueue);\n if (!queued || answered) return;\n wait.timer = setTimeout(() => {\n answered = true;\n leaveQueue(pool, waiting);\n callback(poolWaitTimeoutError(boundMs));\n }, boundMs);\n };\n pool.getConnection = bounded;\n return pool;\n}\n","/**\n * @nextlyhq/adapter-mysql\n *\n * MySQL database adapter for Nextly.\n * Extends DrizzleAdapter from @nextlyhq/adapter-drizzle to provide MySQL-specific functionality.\n *\n * @remarks\n * This adapter uses the mysql2 package for database connectivity and provides:\n * - Connection pooling via mysql2 Pool\n * - Full transaction support with isolation levels\n * - CRUD operations with workarounds for missing RETURNING clause\n * - MySQL-specific error classification\n * - Automatic retry for deadlocks (error 1213)\n *\n * @example\n * ```typescript\n * import { createMySqlAdapter } from '@nextlyhq/adapter-mysql';\n *\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * });\n *\n * await adapter.connect();\n *\n * // Query data\n * const users = await adapter.select('users', {\n * where: { and: [{ column: 'status', op: '=', value: 'active' }] },\n * limit: 10,\n * });\n *\n * await adapter.disconnect();\n * ```\n *\n * @packageDocumentation\n */\n\nimport { DrizzleAdapter } from \"@nextlyhq/adapter-drizzle\";\n// F17: connect-time DB version check shared across all adapters.\nimport {\n createDatabaseError,\n isDatabaseError,\n type MySqlAdapterConfig,\n type ConnectionInspection,\n type DatabaseCapabilities,\n type PoolStats,\n type TransactionContext,\n type TransactionOptions,\n type SessionOptions,\n type StatementSession,\n type SqlParam,\n type WhereClause,\n type WhereCondition,\n type WhereOperator,\n type SelectOptions,\n type InsertOptions,\n type UpdateOptions,\n type DeleteOptions,\n type UpsertOptions,\n type OrderBySpec,\n type JoinSpec,\n type DatabaseError,\n type DatabaseErrorKind,\n type BaseAdapterConfig,\n type AdapterLogger,\n type PoolConfig,\n type SslConfig,\n} from \"@nextlyhq/adapter-drizzle/types\";\nimport {\n acceptsForShare,\n checkDialectVersion,\n} from \"@nextlyhq/adapter-drizzle/version-check\";\nimport { and, eq, getColumns } from \"drizzle-orm\";\nimport type { AnyRelations, Column, SQL } from \"drizzle-orm\";\nimport { getTableConfig, type MySqlTable } from \"drizzle-orm/mysql-core\";\nimport { drizzle, type MySql2Database } from \"drizzle-orm/mysql2\";\nimport type {\n Pool as CallbackPool,\n Connection as CallbackConnection,\n} from \"mysql2\";\nimport mysql from \"mysql2/promise\";\nimport type {\n ConnectionOptions,\n PoolOptions,\n RowDataPacket,\n ResultSetHeader,\n} from \"mysql2/promise\";\n\nimport { boundPoolWait, type QueueingPool } from \"./pool-wait\";\n\n// mysql2 type definitions use mixin patterns that TypeScript struggles with.\n// We define explicit interfaces for the methods we need.\n\n/**\n * Query result type - either rows or a result header\n */\ntype QueryResult = RowDataPacket[] | RowDataPacket[][] | ResultSetHeader;\n\n/**\n * Queryable interface for mysql2 connections\n */\ninterface Queryable {\n query<T extends QueryResult>(sql: string): Promise<[T, unknown]>;\n query<T extends QueryResult>(\n sql: string,\n values: unknown[]\n ): Promise<[T, unknown]>;\n}\n\n/**\n * mysql2 Pool interface with query method\n */\ninterface Pool extends Queryable {\n getConnection(): Promise<PoolConnection>;\n end(): Promise<void>;\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n}\n\n/**\n * A mysql2 connection of its own, outside the pool\n */\ninterface OwnConnection extends Queryable {\n /** Closes the connection. */\n destroy(): void;\n}\n\n/**\n * mysql2 PoolConnection interface with query and release methods\n */\ninterface PoolConnection extends Queryable {\n /** Returns the connection to the pool, for the next request to use. */\n release(): void;\n /** Closes the connection and removes it from the pool. */\n destroy(): void;\n}\n\n/**\n * What a transaction changed on its connection's session, to put back before\n * the connection returns to the pool; null when it changed nothing.\n *\n * MySQL has no transaction-scoped form of `innodb_lock_wait_timeout` or\n * `sql_mode`: a `SET SESSION` outlives the transaction, and the pooled\n * connection would carry one transaction's setting into every later query\n * handed it. Each field present is the session's value from before the\n * change, or null when it could not be read, and then the connection cannot\n * be put back.\n */\ntype SessionRestore = {\n lockWaitTimeout?: number | null;\n sqlMode?: string | null;\n} | null;\n\n/**\n * Puts back what a transaction changed on its connection's session. False\n * when that could not be done, and the connection must not be pooled.\n */\nasync function restoreSession(\n connection: PoolConnection,\n restore: SessionRestore\n): Promise<boolean> {\n if (restore === null) return true;\n if (restore.lockWaitTimeout === null || restore.sqlMode === null) {\n return false;\n }\n try {\n if (restore.lockWaitTimeout !== undefined) {\n // An integer read from the server, never caller input.\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`\n );\n }\n if (restore.sqlMode !== undefined) {\n // Read from the server, and bound as a value all the same.\n await connection.query(\"SET SESSION sql_mode = ?\", [restore.sqlMode]);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ends a transaction's hold on its connection: back to the pool once its\n * session is as the transaction found it, or closed when it is not. A\n * connection whose ROLLBACK failed (`rollbackFailed`) may still be inside\n * the transaction, and the next request handed it would write into a\n * transaction nobody commits.\n */\nasync function returnTransactionConnection(\n connection: PoolConnection,\n rollbackFailed: boolean,\n restore: SessionRestore\n): Promise<void> {\n if (!rollbackFailed && (await restoreSession(connection, restore))) {\n connection.release();\n } else {\n connection.destroy();\n }\n}\n\n/**\n * A newly opened pooled connection, as far as the time-zone pin needs one.\n *\n * The callback form of `query` is load-bearing twice: a pin that failed with\n * nothing handling the error would end up an unhandled `error` event on the\n * connection, and a failure swallowed without this `destroy` would leave an\n * unpinned session in the pool to serve shifted values.\n */\ninterface PinnableConnection {\n query(sql: string, callback: (error: Error | null) => void): void;\n destroy(): void;\n}\n\n/**\n * The part of mysql2's callback pool the time-zone pin uses. Declared here\n * because mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\ninterface PinnablePool {\n on(\n event: \"connection\",\n listener: (connection: PinnableConnection) => void\n ): unknown;\n}\n\n/**\n * mysql2 accepts only `local`, `Z` and `±HH:MM` spellings for its driver\n * zone, and the ones that resolve to UTC are exactly these. `local` resolves\n * to the Node process's own zone, so it agrees with a UTC session only on a\n * UTC host.\n */\nfunction isUtcDriverZone(zone: string): boolean {\n if (zone === \"local\") return new Date().getTimezoneOffset() === 0;\n return zone === \"Z\" || zone === \"+00:00\" || zone === \"-00:00\";\n}\n\n// Re-export types for convenience\nexport type {\n MySqlAdapterConfig,\n DatabaseCapabilities,\n PoolStats,\n TransactionContext,\n TransactionOptions,\n SqlParam,\n WhereClause,\n WhereCondition,\n WhereOperator,\n SelectOptions,\n InsertOptions,\n UpdateOptions,\n DeleteOptions,\n UpsertOptions,\n OrderBySpec,\n JoinSpec,\n DatabaseError,\n DatabaseErrorKind,\n BaseAdapterConfig,\n AdapterLogger,\n PoolConfig,\n SslConfig,\n};\n\n/**\n * Package version\n */\nexport const VERSION = \"0.1.0\";\n\n/**\n * Default pool configuration values.\n */\nconst DEFAULT_POOL_CONFIG = {\n min: 2,\n max: 10,\n idleTimeoutMs: 30000,\n // How long a request waits for a connection, in the queue or opening one:\n // the same bound, and the same default, as the PostgreSQL adapter's.\n connectionTimeoutMs: 15000,\n};\n\n/**\n * MySQL error codes mapping to DatabaseErrorKind.\n *\n * @see https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html\n */\nconst MYSQL_ERROR_CODES: Record<number, DatabaseErrorKind> = {\n // Unique/Duplicate key violations\n 1022: \"unique_violation\", // ER_DUP_KEY\n 1062: \"unique_violation\", // ER_DUP_ENTRY\n 1169: \"unique_violation\", // ER_DUP_UNIQUE\n 1586: \"unique_violation\", // ER_DUP_ENTRY_WITH_KEY_NAME\n\n // Foreign key violations\n 1216: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW\n 1217: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED\n 1451: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED_2\n 1452: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW_2\n\n // Not null violations\n 1048: \"not_null_violation\", // ER_BAD_NULL_ERROR\n 1364: \"not_null_violation\", // ER_NO_DEFAULT_FOR_FIELD\n\n // Check constraint violations (MySQL 8.0.16+)\n 3819: \"check_violation\", // ER_CHECK_CONSTRAINT_VIOLATED\n\n // Deadlock\n 1213: \"deadlock\", // ER_LOCK_DEADLOCK\n\n // Timeout\n 1205: \"timeout\", // ER_LOCK_WAIT_TIMEOUT\n\n // Connection errors\n 1040: \"connection\", // ER_CON_COUNT_ERROR - Too many connections\n 1042: \"connection\", // ER_BAD_HOST_ERROR\n 1043: \"connection\", // ER_HANDSHAKE_ERROR\n 1044: \"connection\", // ER_DBACCESS_DENIED_ERROR\n 1045: \"connection\", // ER_ACCESS_DENIED_ERROR\n 1129: \"connection\", // ER_HOST_IS_BLOCKED\n 1130: \"connection\", // ER_HOST_NOT_PRIVILEGED\n 2002: \"connection\", // CR_CONNECTION_ERROR\n 2003: \"connection\", // CR_CONN_HOST_ERROR\n 2006: \"connection\", // CR_SERVER_GONE_ERROR\n 2013: \"connection\", // CR_SERVER_LOST\n\n // Query errors\n 1064: \"query\", // ER_PARSE_ERROR\n 1146: \"query\", // ER_NO_SUCH_TABLE\n 1054: \"query\", // ER_BAD_FIELD_ERROR\n};\n\n/** ER_LOCK_DEADLOCK: the transaction lost a race, and running it again may succeed. */\nconst DEADLOCK = 1213;\n\n/**\n * The MySQL error number an error carries: its own, or that of the driver\n * error it wraps, as a classified or a Drizzle query error does.\n */\nfunction errnoOf(error: unknown): number | undefined {\n for (let at: unknown = error, hops = 0; hops < 4; hops++) {\n if (typeof at !== \"object\" || at === null) return undefined;\n const errno: unknown = Reflect.get(at, \"errno\");\n if (typeof errno === \"number\") return errno;\n at = Reflect.get(at, \"cause\");\n }\n return undefined;\n}\n\n/**\n * MySQL database adapter for Nextly.\n *\n * Extends the base DrizzleAdapter to provide MySQL-specific functionality\n * using the mysql2 package.\n *\n * @remarks\n * MySQL has some limitations compared to PostgreSQL:\n * - No native RETURNING clause (requires INSERT then SELECT)\n * - No native ILIKE (uses LOWER() LIKE workaround)\n * - No native JSONB (uses JSON type)\n * - No array types\n * - Savepoints disabled for safety (MySQL has nested transaction quirks)\n *\n * @example\n * ```typescript\n * const adapter = new MySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * pool: { max: 20 },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport class MySqlAdapter extends DrizzleAdapter {\n // getDrizzle memoization: drizzle v1's constructor builds a relational\n // query builder per table in the relations config (~40 tables), and the\n // service layer resolves an instance on every db access — construct once\n // per relations object (identity-stable: the schema registry caches it\n // and hands out a NEW object on invalidation, which naturally misses\n // this cache and produces a fresh instance).\n private drizzleByRelations = new WeakMap<AnyRelations, unknown>();\n private drizzleBare: unknown;\n\n /**\n * The database dialect - always 'mysql' for this adapter.\n */\n readonly dialect = \"mysql\" as const;\n\n /**\n * Adapter configuration.\n */\n protected readonly config: MySqlAdapterConfig;\n\n /**\n * Connection pool instance.\n */\n private pool: Pool | null = null;\n\n /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */\n private readonly sessions = new Set<Promise<unknown>>();\n\n /**\n * Connection state flag.\n */\n private connected = false;\n\n /**\n * Whether the server accepts `SELECT ... FOR SHARE`, read from its version\n * string at connect. Undefined until then, which callers treat as not known.\n */\n private sharedRowLock: boolean | undefined;\n private functionalIndexes: boolean | undefined;\n\n /**\n * Creates a new MySQL adapter instance.\n *\n * @param config - Adapter configuration\n */\n constructor(config: MySqlAdapterConfig) {\n super();\n this.config = config;\n }\n\n /**\n * Connect to the MySQL database.\n * Creates a connection pool using mysql2.\n *\n * @remarks\n * This method initializes the connection pool and verifies connectivity\n * by executing a simple query. It is idempotent - calling it multiple\n * times will not create multiple pools.\n *\n * @throws {DatabaseError} If connection fails\n */\n async connect(): Promise<void> {\n if (this.connected && this.pool) {\n return;\n }\n\n try {\n // Warned once, here, rather than on every session configuration the\n // standalone connections build: the configuration cannot change\n // between calls.\n this.warnOnDriverZoneDisagreement();\n const poolConfig = this.buildPoolConfig();\n const pool = mysql.createPool(poolConfig);\n // Pin the session time zone the moment each pooled connection opens.\n // Left unpinned, a session serves in whatever zone the server starts it\n // in — `SYSTEM` on the containers, anything on a self-hosted server —\n // and writes, window bounds and session-zone computations all follow\n // it. mysql2 emits `connection` when a physical connection is\n // established and BEFORE it reaches any caller, and each connection\n // queues its commands in order, so this SET has always run by the time\n // the first query handed the connection is sent; a replaced connection\n // is pinned again by the same listener.\n (pool.pool as unknown as PinnablePool).on(\"connection\", connection => {\n connection.query(\"SET SESSION time_zone = '+00:00'\", error => {\n // A session that could not be pinned must never be pooled: it\n // would serve in whatever zone it answers in.\n if (error) connection.destroy();\n });\n });\n // mysql2 queues a request for a connection without limit; it fails as\n // busy past the same bound a new connection gets to open.\n boundPoolWait(\n pool.pool as unknown as QueueingPool,\n poolConfig.connectTimeout ?? 0\n );\n // Cast to our Pool interface - mysql2's mixin types don't resolve properly\n this.pool = pool as unknown as Pool;\n\n // Verify connection with smoke test, then check dialect version.\n // Why: F17 hard-fails at connect on real MySQL <8.0 (no variant\n // token detected). Recognized variants (MariaDB, TiDB, Aurora,\n // PlanetScale, Vitess) log a warning via the adapter logger and\n // proceed. Truly unparseable strings hard-fail so users see the\n // issue at boot rather than mid-apply.\n const connection = await this.pool.getConnection();\n try {\n await connection.query(\"SELECT 1\");\n const { variant, functionalIndexes } = await checkDialectVersion(\n connection,\n \"mysql\",\n {\n // Why: route variant warnings through the adapter's logger so\n // users see a single, consistent log surface.\n onWarning: msg => this.config.logger?.warn?.(msg),\n }\n );\n // MariaDB and TiDB connect through this adapter too, and reject\n // `FOR SHARE`; the capability tells callers which lock they can take.\n this.sharedRowLock = acceptsForShare(variant);\n // Whether a unique index may key on an expression, which per-language uniqueness of a\n // translated value does.\n this.functionalIndexes = functionalIndexes;\n this.connected = true;\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection established\", {\n host: this.config.host ?? \"from URL\",\n database: this.config.database ?? \"from URL\",\n });\n }\n } finally {\n connection.release();\n }\n } catch (error) {\n // Clean up on failure\n if (this.pool) {\n await this.pool.end().catch(() => {});\n this.pool = null;\n }\n throw this.classifyError(error);\n }\n }\n\n /**\n * Disconnect from the MySQL database.\n * Gracefully closes the connection pool.\n *\n * @remarks\n * This method is idempotent - calling it multiple times is safe.\n * It waits for all connections to be released before shutting down,\n * including each running `withSession`'s connection of its own.\n */\n async disconnect(): Promise<void> {\n // Detach the pool FIRST, then drop the memoized drizzle instances —\n // this closes the repopulation window where a concurrent getDrizzle()\n // call during the (async) pool.end() could cache an instance wrapping\n // the closing pool.\n const pool = this.pool;\n this.pool = null;\n this.drizzleBare = undefined;\n this.drizzleByRelations = new WeakMap();\n if (!pool) {\n return;\n }\n\n try {\n // With the pool detached no session starts; those running end first.\n await Promise.allSettled([...this.sessions]);\n await pool.end();\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection closed\");\n }\n } finally {\n this.connected = false;\n }\n }\n\n /**\n * Check if connected to the database.\n */\n isConnected(): boolean {\n return this.connected && this.pool !== null;\n }\n\n /**\n * Get connection pool statistics.\n * Returns null if not connected.\n *\n * @remarks\n * MySQL2 pool exposes different stats than pg:\n * - _allConnections: all connections\n * - _freeConnections: idle connections\n * - _connectionQueue: waiting requests\n */\n getPoolStats(): PoolStats | null {\n if (!this.pool) {\n return null;\n }\n\n // mysql2 Pool internal properties (cast to access internals)\n const poolInternal = this.pool as unknown as {\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n };\n\n const internal = poolInternal.pool;\n if (!internal) {\n return {\n total: 0,\n idle: 0,\n waiting: 0,\n active: 0,\n };\n }\n\n const total = internal._allConnections?.length ?? 0;\n const idle = internal._freeConnections?.length ?? 0;\n const waiting = internal._connectionQueue?.length ?? 0;\n\n return {\n total,\n idle,\n waiting,\n active: total - idle,\n };\n }\n\n /**\n * The connection as a health check reads it, on one pooled connection: the\n * server's version, timed for the round trip, then that session's\n * `Ssl_cipher` status, which names the cipher when the session's connection\n * is encrypted and is empty when it is not.\n *\n * @remarks\n * The status and the connection's own socket are weighed by\n * `encryptionOf`: behind a proxy the status can describe the proxy's own\n * connection to the server, while this connection's socket is TLS. The\n * socket is read as it is, never inferred from the `ssl` setting.\n *\n * Whether the database is on this machine is judged from the configured\n * address, which never leaves this method. Any failure answers `\"unknown\"`.\n */\n override async inspectConnection(): Promise<ConnectionInspection> {\n const local = this.isLocalHost(this.connectionHost());\n let connection: PoolConnection | undefined;\n try {\n connection = await this.ensurePool().getConnection();\n const started = performance.now();\n const [versionRows] = await connection.query<RowDataPacket[]>(\n \"SELECT VERSION() AS version\"\n );\n const roundTripMs = Math.max(0, Math.round(performance.now() - started));\n const [statusRows] = await connection.query<RowDataPacket[]>(\n \"SHOW SESSION STATUS LIKE 'Ssl_cipher'\"\n );\n const cipher: unknown = statusRows[0]?.Value;\n const version: unknown = versionRows[0]?.version;\n return {\n encryption: this.encryptionOf({\n session: typeof cipher === \"string\" ? cipher !== \"\" : undefined,\n ownSocketTls: this.ownSocketIsTls(connection),\n }),\n local,\n roundTripMs,\n ...(typeof version === \"string\" && version !== \"\"\n ? { serverVersion: version.split(\"-\")[0] }\n : {}),\n };\n } catch {\n return { encryption: \"unknown\", local };\n } finally {\n connection?.release();\n }\n }\n\n /**\n * The host this adapter connects to, as mysql2 resolves it: the connection\n * string's when one is set, else the `host` option, else `localhost`.\n * `undefined` when the connection string cannot be read.\n */\n private connectionHost(): string | undefined {\n const configured = this.config.url\n ? this.hostOfUrl(this.config.url)\n : (this.config.host ?? \"\");\n return configured === \"\" ? \"localhost\" : configured;\n }\n\n /**\n * Execute a raw SQL query.\n *\n * @param sql - SQL query string with ? placeholders\n * @param params - Query parameters\n * @returns Query results\n *\n * @throws {DatabaseError} If query execution fails\n */\n async executeQuery<T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> {\n const pool = this.ensurePool();\n const startTime = Date.now();\n\n try {\n // Outside the calling flow's transaction, if it has one (`notePoolAccess`).\n this.notePoolAccess(sql);\n const [rows] = await pool.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n\n // Log query if logger configured\n if (this.config.logger?.query) {\n const durationMs = Date.now() - startTime;\n this.config.logger.query(sql, params, durationMs);\n }\n\n return rows as T[];\n } catch (error) {\n throw this.classifyError(error, sql);\n }\n }\n\n /**\n * Execute work within a transaction.\n *\n * @param work - Function containing transactional operations\n * @param options - Transaction options (isolation level, timeout, retry)\n * @returns Result of the work function\n *\n * @remarks\n * MySQL transactions support isolation levels. Automatic retry is\n * implemented for deadlocks (error 1213) when `retryCount` is specified.\n *\n * Note: Savepoints are disabled in this adapter for safety due to\n * MySQL's quirks with nested transactions.\n *\n * A call made inside another transaction's work therefore joins it flat,\n * on its connection: its statements are the outer's. Its failure cannot be\n * undone alone, so it fails the whole transaction, even when the outer\n * catches it: the outer's COMMIT is refused, it rolls back, and it rejects\n * with the error that failed the joined call; any later joined call fails\n * with that error at once. A joined call keeps the outer's options and is\n * never retried itself. `retryWhen` is asked first, of every error that\n * ends an attempt, application ones included.\n */\n transaction<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options?: TransactionOptions\n ): Promise<T> {\n return this.openOrJoin(work, options, {\n run: (_attempt, openedAt) => this.runAttempt(work, options, openedAt),\n retryable: error => errnoOf(error) === DEADLOCK,\n describe: error => {\n const errno = errnoOf(error);\n return {\n label: errno === DEADLOCK ? \"deadlock\" : \"a retryable error\",\n detail: { errno },\n };\n },\n logger: this.config.logger,\n });\n }\n\n /**\n * Runs `work` on a connection of its own, opened for it with the pool's\n * connection settings, outside a transaction, each statement committing as\n * it runs.\n *\n * @remarks\n * Not a pooled connection: on a server whose sessions start with autocommit\n * off, one can hold a transaction an earlier caller left open, and turning\n * autocommit on would commit that caller's writes. A new connection holds\n * nothing. It does not count against `pool.max`.\n *\n * Each statement commits as it runs, whatever the server's own autocommit\n * default. With `strictSqlMode` the session runs in strict mode, as a\n * transaction given the same option does, so a statement that would cut a\n * value short fails and changes nothing. The connection is closed\n * afterwards, whether `work` finished or threw, so whatever `work` set on\n * its session (a temporary table, a session variable, a lock) ends with it.\n */\n override async withSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n // A precondition: before any connection is opened.\n this.refuseSessionInTransaction();\n // Connected first, as every other call requires.\n this.ensurePool();\n // Tracked before its connection opens: `disconnect` waits for it.\n const running = this.runSession(work, options);\n this.sessions.add(running);\n try {\n return await running;\n } finally {\n this.sessions.delete(running);\n }\n }\n\n /** `withSession`'s work, on a connection it opens and closes. */\n private async runSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n let connection: OwnConnection;\n try {\n // mysql2's connection already satisfies `OwnConnection`, so it is assigned as it comes.\n connection = await mysql.createConnection(this.buildConnectionConfig());\n } catch (error) {\n throw this.classifyError(error);\n }\n try {\n try {\n // A server whose sessions start with autocommit off would otherwise\n // hold every statement in a transaction nobody commits.\n await connection.query(\"SET SESSION autocommit = 1\");\n // The connection opened outside the pool, so the pool's `connection`\n // listener never pins it; its driver already parses in UTC, and a\n // session still in the server's zone would shift every value a\n // round-trip fails to cancel. The work below never runs unpinned.\n await connection.query(\"SET SESSION time_zone = '+00:00'\");\n if (options?.strictSqlMode) {\n // Nothing to put back: the connection is closed afterwards.\n await this.enterStrictMode(connection, {});\n }\n } catch (error) {\n throw this.classifyError(error);\n }\n return await work({\n execute: async <R = unknown>(\n statement: string,\n params: SqlParam[] = []\n ): Promise<R[]> => {\n const startTime = Date.now();\n try {\n const [rows] = await connection.query<RowDataPacket[]>(\n statement,\n params as unknown[]\n );\n if (this.config.logger?.query) {\n this.config.logger.query(\n statement,\n params,\n Date.now() - startTime\n );\n }\n return rows as R[];\n } catch (error) {\n throw this.classifyError(error, statement);\n }\n },\n });\n } finally {\n connection.destroy();\n }\n }\n\n /** One attempt at an outermost transaction, on a connection of its own. */\n private async runAttempt<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options: TransactionOptions | undefined,\n openedAt: string | undefined\n ) {\n const pool = this.ensurePool();\n // A checkout, taken from the pool like `executeQuery`'s queries are.\n this.notePoolAccess();\n const connection = await pool.getConnection();\n // Set when the ROLLBACK failed (`returnTransactionConnection`).\n let rollbackFailed = false;\n // What `beginTransaction` changed on the session, to put back once the\n // transaction has ended. Filled in as each change lands, so a setup that\n // fails part way still puts back what it had already changed.\n const restore: NonNullable<SessionRestore> = {};\n const settled = await this.settleOutermost(\n {\n context: this.createTransactionContext(connection),\n join: this.joinSteps(connection),\n openedAt,\n begin: () => this.beginTransaction(connection, options, restore),\n commit: async () => {\n await connection.query(\"COMMIT\");\n },\n // A failure is recorded for the return and rethrown, so the base\n // knows the ROLLBACK is unconfirmed.\n rollback: async () => {\n await connection.query(\"ROLLBACK\").catch((rollbackError: unknown) => {\n rollbackFailed = true;\n throw rollbackError;\n });\n },\n },\n work,\n options\n );\n // Returned before anything held runs, so an effect that opens a\n // transaction takes one connection, not two.\n await returnTransactionConnection(connection, rollbackFailed, restore);\n return settled;\n }\n\n /**\n * How a call made inside a transaction on `connection` joins it: flat, on\n * the same connection, with nothing to send. Any failure inside it fails\n * the whole transaction, since no savepoint can undo the part alone.\n */\n private joinSteps(connection: PoolConnection) {\n const callbackConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n return this.joinStepsOn(\n () => this.createTransactionContext(connection),\n relations =>\n relations\n ? drizzle({ client: callbackConnection, relations })\n : drizzle({ client: callbackConnection }),\n context => ({\n context,\n begin() {},\n commit() {},\n rollback() {},\n dooms: () => true,\n })\n );\n }\n\n /**\n * Get MySQL database capabilities.\n *\n * @remarks\n * MySQL has some limitations:\n * - No JSONB (uses JSON)\n * - No arrays\n * - No native ILIKE\n * - No RETURNING clause\n * - Savepoints disabled for safety\n */\n getCapabilities(): DatabaseCapabilities {\n return {\n dialect: \"mysql\",\n supportsJsonb: false, // MySQL uses JSON, not JSONB\n supportsJson: true,\n supportsArrays: false, // MySQL doesn't support array types\n supportsGeneratedColumns: true, // MySQL 5.7.6+\n supportsFts: true, // MySQL has FULLTEXT indexes\n supportsIlike: false, // No native ILIKE, use LOWER() LIKE\n supportsReturning: false, // No RETURNING clause in MySQL\n supportsSavepoints: false, // Disabled for safety per approved approach\n supportsOnConflict: true, // ON DUPLICATE KEY UPDATE\n maxParamsPerQuery: 65535, // MySQL limit\n maxIdentifierLength: 64, // MySQL limit\n sharedRowLock: this.sharedRowLock, // Known once connected\n functionalIndexes: this.functionalIndexes, // Known once connected\n };\n }\n\n /**\n * Build a placeholder for MySQL (uses ? instead of $1, $2, etc.)\n *\n * @param _index - Parameter index (ignored for MySQL)\n * @returns The ? placeholder\n */\n protected buildPlaceholder(_index: number): string {\n return \"?\";\n }\n\n /**\n * Build multiple placeholders for MySQL.\n *\n * @param count - Number of placeholders needed\n * @param _startIndex - Starting index (ignored for MySQL)\n * @returns Comma-separated ? placeholders\n */\n protected buildPlaceholders(count: number, _startIndex: number = 0): string {\n return Array(count).fill(\"?\").join(\", \");\n }\n\n /**\n * Escape an identifier for MySQL (uses backticks instead of double quotes).\n *\n * @param identifier - The identifier to escape\n * @returns Escaped identifier with backticks\n */\n protected escapeIdentifier(identifier: string): string {\n // MySQL uses backticks for identifiers\n return `\\`${identifier.replace(/`/g, \"``\")}\\``;\n }\n\n // ============================================================\n // Protected Helper Methods\n // ============================================================\n\n /**\n * Ensures pool is connected and returns it.\n *\n * @throws {DatabaseError} If not connected\n */\n private ensurePool(): Pool {\n if (!this.pool) {\n throw createDatabaseError({\n kind: \"connection\",\n message: \"MySqlAdapter is not connected. Call connect() first.\",\n });\n }\n return this.pool;\n }\n\n /**\n * Return the typed Drizzle instance for MySQL.\n * Guarded for server-only usage and requires an active connection.\n *\n * @param schema - Optional schema for relational queries (db.query.*)\n * @returns Drizzle ORM instance wrapping the mysql2 pool connection\n * @throws {Error} If called in browser or not connected\n */\n getDrizzle<T = MySql2Database<AnyRelations>>(relations?: AnyRelations): T {\n if (typeof window !== \"undefined\") {\n throw new Error(\"getDrizzle() is server-only\");\n }\n const pool = this.ensurePool();\n if (!relations) {\n this.drizzleBare ??= this.poolDrizzle(pool);\n return this.drizzleBare as T;\n }\n let cached = this.drizzleByRelations.get(relations);\n if (!cached) {\n cached = this.poolDrizzle(pool, relations);\n this.drizzleByRelations.set(relations, cached);\n }\n return cached as T;\n }\n\n /**\n * A Drizzle handle on the pool, for `getDrizzle`.\n *\n * Its `transaction` runs on a connection this adapter checks out and\n * returns itself (`pinDrizzleTransactions`): destroyed rather than pooled\n * when no ROLLBACK could be confirmed on it. And it counts for\n * `transactionOpenInFlow`, as the adapter's does.\n */\n private poolDrizzle(pool: Pool, relations?: AnyRelations) {\n // drizzle v1's mysql2 driver takes the CALLBACK pool and connection,\n // which the mysql2/promise wrappers expose on `.pool` and `.connection`:\n // handed a wrapper, it throws (\"Cannot set properties of undefined\n // (setting 'supportBigNumbers')\"). The pre-v1 `mode` option is gone.\n const onClient = (client: CallbackPool | CallbackConnection) =>\n relations ? drizzle({ client, relations }) : drizzle({ client });\n // The handle's own queries go to the pool, so it is handed a view of the\n // pool that reports them (`observedPool`).\n const callbackPool = this.observedPool(\n (pool as unknown as { pool: CallbackPool }).pool,\n [\"query\", \"execute\", \"getConnection\"]\n );\n // Its transactions join the one the calling flow is inside, as the\n // adapter's `transaction()` does, and open one of this adapter's\n // otherwise (`joiningHandle`).\n return this.joiningHandle(\n onClient(callbackPool),\n async () => {\n const connection = await pool.getConnection();\n return {\n drizzle: onClient(\n (connection as unknown as { connection: CallbackConnection })\n .connection\n ),\n rollback: () => connection.query(\"ROLLBACK\"),\n release: dirty => {\n if (dirty) connection.destroy();\n else connection.release();\n },\n join: this.joinSteps(connection),\n };\n },\n relations\n );\n }\n\n /**\n * Warns when a caller's configured driver zone does not agree with the\n * pinned UTC session.\n *\n * mysql2's driver-level `timezone` decides how it parses the date strings\n * the pinned session answers with and how it binds raw Date parameters, so\n * a non-UTC spelling there would shift values a round-trip fails to\n * cancel. The configuration is aligned to the pin (`buildConnectionConfig`\n * sets UTC unconditionally, for pooled and standalone connections alike),\n * never rewritten silently: the caller configured a reading, and this is\n * where that reading is named as lost. Runs from `connect`, once per pool\n * build — the configuration cannot change between calls.\n */\n private warnOnDriverZoneDisagreement(): void {\n // mysql2's own default is `local`, so an unset configuration is judged\n // the same way: it agrees only on a UTC host.\n const requestedZone = this.config.timezone ?? \"local\";\n if (isUtcDriverZone(requestedZone)) return;\n this.config.logger?.warn?.(\n `Driver timezone \"${requestedZone}\" is not UTC; it is aligned to the pinned UTC session and no longer shifts reads or writes.`\n );\n }\n\n /**\n * Builds mysql2 Pool configuration from adapter config.\n */\n private buildPoolConfig(): PoolOptions {\n const config: PoolOptions = this.buildConnectionConfig();\n\n // Pool settings - mysql2 uses different property names\n config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;\n config.idleTimeout =\n this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;\n\n // A request waits for a connection when the pool is full. The queue has\n // no length limit; each wait in it is bounded instead (`boundPoolWait`).\n config.waitForConnections = true;\n config.queueLimit = 0;\n\n return config;\n }\n\n /**\n * Builds the settings every connection opens with, pooled or not, from\n * adapter config.\n */\n private buildConnectionConfig(): ConnectionOptions {\n const config: ConnectionOptions = this.buildAddressConfig();\n\n config.connectTimeout =\n this.config.pool?.connectionTimeoutMs ??\n DEFAULT_POOL_CONFIG.connectionTimeoutMs;\n\n // SSL configuration\n if (this.config.ssl) {\n config.ssl = this.buildSslConfig(this.config.ssl);\n }\n\n // MySQL-specific options. Every connection this adapter opens serves a\n // UTC session (see the pins in `connect` and `runSession`), so the\n // driver zone is pinned with them: mysql2 parses the date strings a\n // pinned session answers with and binds raw Date parameters, and a\n // driver in one zone beside a session in another shifts values every\n // time a round-trip fails to cancel. A configured zone that is not UTC\n // is aligned rather than refused — the pin is the deliberate boundary —\n // and `connect` warns the caller, once, that the shift they configured\n // is gone.\n config.timezone = \"Z\";\n\n if (this.config.charset) {\n config.charset = this.config.charset;\n }\n\n // Enable multiple statements if needed (disabled by default for security)\n config.multipleStatements = false;\n\n // Date handling\n config.dateStrings = false; // Return Date objects\n\n return config;\n }\n\n /** Where to connect: the connection string, or the explicit options. */\n private buildAddressConfig(): ConnectionOptions {\n if (this.config.url) return { uri: this.config.url };\n const { host, port, database, user, password } = this.config;\n const config: ConnectionOptions = { host, port, database, user, password };\n // An option left unset is left out, for mysql2's own default.\n for (const key of Object.keys(config) as (keyof ConnectionOptions)[]) {\n if (!config[key]) delete config[key];\n }\n return config;\n }\n\n /** mysql2's `ssl` option for the adapter's `ssl` setting. */\n private buildSslConfig(\n ssl: NonNullable<MySqlAdapterConfig[\"ssl\"]>\n ): ConnectionOptions[\"ssl\"] {\n if (typeof ssl === \"boolean\") {\n return ssl ? {} : undefined;\n }\n return {\n rejectUnauthorized: ssl.rejectUnauthorized,\n ca: ssl.ca,\n cert: ssl.cert,\n key: ssl.key,\n };\n }\n\n /**\n * Begins a transaction with the specified options, recording in `changed`\n * each session setting it changes, as it changes it, for `restoreSession`\n * to put back.\n */\n private async beginTransaction(\n connection: PoolConnection,\n options: TransactionOptions | undefined,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n // Set isolation level if specified (must be done before BEGIN)\n if (options?.isolationLevel) {\n const isolationMap: Record<string, string> = {\n \"read uncommitted\": \"READ UNCOMMITTED\",\n \"read committed\": \"READ COMMITTED\",\n \"repeatable read\": \"REPEATABLE READ\",\n serializable: \"SERIALIZABLE\",\n };\n const level = isolationMap[options.isolationLevel];\n if (level) {\n await connection.query(`SET TRANSACTION ISOLATION LEVEL ${level}`);\n }\n }\n\n // Set read-only mode if specified\n if (options?.readOnly) {\n await connection.query(\"SET TRANSACTION READ ONLY\");\n }\n\n // Begin the transaction\n await connection.query(\"START TRANSACTION\");\n\n // Set lock wait timeout if specified\n if (options?.timeoutMs) {\n // Read first, so the session's own value can be put back once the\n // transaction has ended (`SessionRestore`).\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout\"\n );\n const previous = Number(rows[0]?.timeout);\n // MySQL uses seconds for lock_wait_timeout\n const timeoutSeconds = Math.ceil(options.timeoutMs / 1000);\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`\n );\n changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;\n }\n\n if (options?.strictSqlMode) {\n await this.enterStrictMode(connection, changed);\n }\n }\n\n /**\n * Strict mode for the rest of this connection's session: an INSERT, UPDATE\n * or ALTER that would cut a string to its column's length, or clamp a\n * number to its range, fails instead of applying with a warning. Added to\n * the session's own mode, so every other flag it had still holds, and\n * recorded in `changed` for `restoreSession` to put back.\n */\n private async enterStrictMode(\n connection: Queryable,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.sql_mode AS mode\"\n );\n const previous: unknown = rows[0]?.mode;\n // Recorded before the SET: one the server applied but whose reply was\n // lost still has its mode put back.\n changed.sqlMode = typeof previous === \"string\" ? previous : null;\n await connection.query(\n \"SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))\"\n );\n }\n\n /**\n * Creates a TransactionContext for the given connection.\n *\n * @remarks\n * Note: Savepoint methods are not implemented (set to undefined)\n * as savepoints are disabled in this adapter per approved approach.\n */\n /**\n * Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL\n * and SQLite, on the tables where MySQL can keep that promise.\n *\n * MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`\n * fires on a collision with ANY unique key, and a failed insert does not\n * say reliably which one. So the conflict target must be the table's only\n * unique key apart from its primary key, and any other table is refused\n * before anything is written. On such a table the row is inserted first —\n * its validation and triggers apply, as they do before `ON CONFLICT`\n * elsewhere — and a duplicate is resolved by a locking read of the target\n * key, which sees the latest committed row rather than the transaction's\n * snapshot. The row found is kept as it is when there is nothing to update\n * (a no-op assignment would still run its update triggers), or updated by\n * its key. No row there means the collision was on the primary key, and it\n * is refused as `ON CONFLICT (target)` refuses it.\n *\n * The steps run in the caller's transaction, or in one of their own, since a\n * locking read locks nothing past its statement under autocommit. Rows are\n * read back by their primary key, so a trigger that rewrites the conflict\n * value still returns the row stored. The conflict columns must also be\n * given values: MySQL cannot look a row up by a key the database would only\n * fill in itself. A conflict is resolved on the caller's key value: a\n * BEFORE INSERT trigger that moves the key onto another row's value is\n * refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a\n * caller's transaction stays usable.\n */\n override async upsert<T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions,\n executor?: unknown\n ): Promise<T> {\n const tableObj = this.getTableObject(table) as MySqlTable | undefined;\n // Unknown table: the base method reports it as every CRUD method does.\n if (!tableObj) return super.upsert<T>(table, data, options, executor);\n let target: { where: SQL | undefined; nullKey: boolean };\n try {\n // Judged before anything else, so a refused call takes no connection.\n target = this.upsertTarget(tableObj, table, data, options);\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n // A locking read holds its lock only inside a transaction: under autocommit\n // it is released when its own statement ends, and the next statement may\n // run on another pooled connection. Without the caller's transaction, the\n // insert, the read and the update get one of their own, on one connection.\n if (executor === undefined) {\n return this.transaction(tx => tx.upsert<T>(table, data, options));\n }\n try {\n const db = executor as MySql2Database<AnyRelations>;\n // A conflict key holding NULL never conflicts: a unique index admits any\n // number of NULLs. It is a plain insert, read back by its primary key.\n if (target.nullKey) {\n return ((await this.insertRow(db, tableObj, data)) ?? data) as T;\n }\n return (await this.insertOrResolve(\n db,\n tableObj,\n data,\n options,\n target.where\n )) as T;\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n }\n\n /**\n * What an upsert is keyed on: the target's condition, and whether its key\n * holds a NULL. Refuses a target this dialect cannot keep to, and a conflict\n * column left for the database to fill — MySQL cannot look a row up by a key\n * value only the database would supply.\n */\n private upsertTarget(\n tableObj: MySqlTable,\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions\n ): { where: SQL | undefined; nullKey: boolean } {\n // MySQL needs every conflict value whether or not it updates: the row is\n // found by them after a duplicate, so the keep-existing rule applies to\n // every call here.\n this.assertUpsertTarget(table, data, options, true);\n const key = this.conflictKey(tableObj, table, options.conflictColumns);\n const values = options.conflictColumns.map(column => data[column]);\n return {\n where: and(...key.map((column, i) => eq(column, values[i] as never))),\n nullKey: values.some(value => value === null),\n };\n }\n\n /**\n * The table's columns for a conflict target, refusing a target MySQL cannot\n * keep to: one that is not a primary or unique key, which an update could\n * spread over several rows, and one beside which the table has another\n * unique key, a collision on which MySQL would not tell apart.\n */\n private conflictKey(\n tableObj: MySqlTable,\n table: string,\n conflictColumns: readonly string[]\n ): Column[] {\n const record = tableObj as unknown as Record<string, Column | undefined>;\n const columns = conflictColumns.map(name => record[name]);\n const names = new Set(columns.map(column => column?.name));\n const isTarget = (key: readonly string[]) =>\n key.length === names.size && key.every(name => names.has(name));\n const { primary, unique } = keysOf(tableObj);\n // Every row this upsert returns is read back by its primary key, so a\n // table without one has no stable way to find the row it wrote.\n if (primary.length === 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`,\n });\n }\n const known = columns.every(column => column !== undefined);\n if (\n !known ||\n !(isTarget(primary) || unique.some(k => isTarget(k.columns)))\n ) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: the conflict columns (${conflictColumns.join(\", \")}) are not a primary or unique key of the table.`,\n });\n }\n const others = unique.filter(key => !isTarget(key.columns));\n if (others.length > 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL cannot limit a duplicate to the conflict columns (${conflictColumns.join(\", \")}) while the table has other unique keys (${others.map(key => key.name).join(\", \")}). Upsert only on a table whose one unique key besides the primary key is the conflict target, or insert and update inside a transaction.`,\n });\n }\n return columns;\n }\n\n /** The update an upsert applies to an existing row: never its key. */\n private updateValues(\n data: Record<string, unknown>,\n options: UpsertOptions\n ): Record<string, unknown> {\n const key = new Set(options.conflictColumns);\n const update: Record<string, unknown> = {};\n for (const column of options.updateColumns ?? Object.keys(data)) {\n if (!key.has(column) && column in data) update[column] = data[column];\n }\n return update;\n }\n\n /** The row matching `where`, by a locking (current) read. */\n private async lockedRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n where: SQL | undefined\n ): Promise<unknown> {\n const rows = await db\n .select()\n .from(tableObj)\n .where(where)\n .limit(1)\n .for(\"update\");\n return rows[0];\n }\n\n /**\n * Insert the row, or resolve a duplicate on the target key. The insert is\n * read back by the primary key it reports, not by the conflict key: a\n * BEFORE INSERT trigger may have rewritten the conflict value, and the row\n * stored is the one to return.\n *\n * A duplicate is resolved when a row holds the TARGET key, whichever unique\n * key MySQL names in its error. PostgreSQL checks the target's index before\n * any other, so a row colliding on the target and on another key resolves\n * on the target there too; refusing it here because MySQL happened to check\n * the other index first would answer that case differently. A target row\n * committed by another transaction after this insert failed is the order in\n * which that transaction ran first, where PostgreSQL also resolves on it.\n */\n private async insertOrResolve(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>,\n options: UpsertOptions,\n where: SQL | undefined\n ): Promise<unknown> {\n let failure: unknown;\n try {\n const stored = await this.insertRow(db, tableObj, data);\n return stored ?? (await this.lockedRow(db, tableObj, where));\n } catch (error) {\n // Classified as everything else is, through the driver's own error\n // under Drizzle's wrapper (`classifyError`).\n if (this.classifyError(error).kind !== \"unique_violation\") throw error;\n failure = error;\n }\n const existing = await this.lockedRow(db, tableObj, where);\n // No row holds the target key: the collision was on another unique key,\n // refused as `ON CONFLICT (target)` refuses it, as the unique violation the\n // classifier reads it to be.\n if (existing === undefined) throw failure;\n const update = this.updateValues(data, options);\n if (Object.keys(update).length === 0) return existing;\n await db.update(tableObj).set(update).where(where);\n // Read back by the row's primary key as the update left it: the update may\n // set the key itself (every given column is updated by default, as `ON\n // CONFLICT DO UPDATE` does elsewhere), and a BEFORE UPDATE trigger may\n // have changed the conflict value the update was keyed on. A trigger that\n // changed the primary key instead leaves the conflict value standing, so\n // the row is looked for there next; one that changed both is not found.\n const identity = this.byIdentity(tableObj, {\n ...(existing as Record<string, unknown>),\n ...update,\n });\n const updated =\n identity === undefined\n ? undefined\n : await this.lockedRow(db, tableObj, identity);\n return updated ?? this.lockedRow(db, tableObj, where);\n }\n\n /**\n * Insert a row and read it back by its primary key: the value the caller\n * gave, or the one MySQL or the model generated, which `$returningId()`\n * reports and a given value it does not. Undefined when the table has no\n * primary key to read by.\n */\n private async insertRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>\n ): Promise<unknown> {\n const [generated] = (await db\n .insert(tableObj)\n .values(data as never)\n .$returningId()) as Record<string, unknown>[];\n // A key the caller gave wins; one given as undefined or null does not hide\n // the value MySQL generated for it.\n const given = Object.fromEntries(\n Object.entries(data).filter(\n ([, value]) => value !== undefined && value !== null\n )\n );\n const identity = this.byIdentity(tableObj, { ...generated, ...given });\n return identity === undefined\n ? undefined\n : this.lockedRow(db, tableObj, identity);\n }\n\n /**\n * A row's primary key as a condition, from a record keyed as the model's\n * properties are. Undefined when the table has no primary key or the record\n * lacks part of it.\n */\n private byIdentity(\n tableObj: MySqlTable,\n record: Record<string, unknown>\n ): SQL | undefined {\n const primary = new Set(keysOf(tableObj).primary);\n const parts = Object.entries(getColumns(tableObj)).filter(([, column]) =>\n primary.has(column.name)\n );\n if (\n parts.length === 0 ||\n parts.some(\n ([name]) => record[name] === undefined || record[name] === null\n )\n ) {\n return undefined;\n }\n return and(\n ...parts.map(([name, column]) => eq(column, record[name] as never))\n );\n }\n\n /** A table-level `primaryKey({ columns })`, read through the MySQL table config. */\n protected override compositePrimaryKey(\n tableObj: Record<string, unknown>\n ): object[] {\n return getTableConfig(\n tableObj as unknown as MySqlTable\n ).primaryKeys.flatMap(key => key.columns);\n }\n\n private createTransactionContext(\n connection: PoolConnection\n ): TransactionContext {\n // Bind a Drizzle instance to this transaction's checked-out connection so\n // the delegated CRUD methods run inside the transaction and see its\n // uncommitted rows. drizzle's mysql2 driver needs the underlying CALLBACK\n // connection, which the mysql2/promise wrapper exposes on `.connection`\n // (mirrors the `.pool` unwrap in getDrizzle()); getDrizzle() itself wraps\n // the pool, which would use a different connection. Built lazily and\n // memoized: transactions that use only raw execute/insert never construct\n // it.\n const txConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n const txHandles = this.transactionDrizzleHandles(relations =>\n relations\n ? drizzle({ client: txConnection, relations })\n : drizzle({ client: txConnection })\n );\n const txDb = txHandles.bare;\n return {\n drizzleWithRelations: txHandles.withRelations,\n\n execute: async <T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> => {\n const [rows] = await connection.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n return rows as T[];\n },\n\n // Run on the transaction-bound Drizzle instance rather than the pool, so\n // the statement is part of this transaction and sees its uncommitted rows.\n runStatement: async (statement: SQL): Promise<void> => {\n await txDb().execute(statement);\n },\n\n // mysql2 answers a `[rows, fields]` tuple; the transaction-bound instance\n // keeps the read inside this transaction so it sees its uncommitted writes.\n queryStatement: async <T = Record<string, unknown>>(\n statement: SQL\n ): Promise<T[]> => {\n const result = await txDb().execute(statement);\n // A tuple's first element is the rows. Anything else was not understood,\n // and must not reach a caller as \"there is nothing there\" — the same\n // reason the pooled `queryStatement` refuses rather than answering\n // empty.\n if (!Array.isArray(result)) {\n throw this.createDatabaseError(\n \"query\",\n \"Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.\",\n undefined\n );\n }\n return result[0] as unknown as T[];\n },\n\n lockRow: async (table: string, id: SqlParam): Promise<void> => {\n const idColumn = this.escapeIdentifier(\"id\");\n await connection.query(\n `SELECT ${idColumn} FROM ${this.escapeIdentifier(table)} ` +\n `WHERE ${idColumn} = ? FOR UPDATE`,\n [id] as unknown[]\n );\n },\n\n insert: async <T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options?: InsertOptions\n ): Promise<T> => {\n // Refused before any statement, as the pooled insert is: a table this\n // instance never registered may not match what its code would write.\n const insertTableObj = this.resolvedTableObject(table);\n const mapped = this.mapRowToRawSql(insertTableObj, data);\n const columns = Object.keys(mapped);\n const values = Object.values(mapped);\n const placeholders = this.buildPlaceholders(values.length, 0);\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES (${placeholders})`;\n\n const [result] = await connection.query<ResultSetHeader>(sql, values);\n\n const ret = options?.returning;\n // No columns requested: skip the select-back reread entirely.\n if (Array.isArray(ret) && ret.length === 0) {\n return undefined as T;\n }\n\n // MySQL has no RETURNING; select the inserted row back. Project only the\n // requested columns so a large JSON snapshot is not read unless asked.\n // Each timestamp's wall clock, spelled out by the database. mysql2\n // turns one into a `Date` in the LOCAL zone before this code sees it,\n // and that conversion cannot be undone: a wall clock inside a\n // daylight-saving gap is normalized away.\n const aliases = this.dateWallClockAliases(insertTableObj, ret ?? \"*\");\n const spelled = aliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const projected =\n !ret || ret === \"*\"\n ? \"*\"\n : this.mapColumnNamesToSql(insertTableObj, ret)\n .map(c => this.escapeIdentifier(c))\n .join(\", \");\n const selectList = spelled ? `${projected}, ${spelled}` : projected;\n\n // Prefer the primary key: auto-increment via insertId, otherwise a\n // supplied id (manually-keyed tables like nextly_versions). Matching by\n // all values is a last resort because `col = NULL` never matches, so a\n // row with nullable columns would not be found by that path.\n const idValue = result.insertId ? result.insertId : mapped.id;\n if (idValue !== undefined) {\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE id = ?`,\n [idValue]\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n }\n\n const whereClauses = columns.map(\n c => `${this.escapeIdentifier(c)} = ?`\n );\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE ${whereClauses.join(\" AND \")} LIMIT 1`,\n values\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n },\n\n insertMany: async <T = unknown>(\n table: string,\n data: Record<string, unknown>[],\n options?: InsertOptions\n ): Promise<T[]> => {\n if (data.length === 0) return [];\n const retMany = options?.returning;\n const skipReread = Array.isArray(retMany) && retMany.length === 0;\n\n // Refused before any statement, as `insert` above is.\n const tableObj = this.resolvedTableObject(table);\n const mappedRecords = data.map(r => this.mapRowToRawSql(tableObj, r));\n const columns = Object.keys(mappedRecords[0]);\n const allValues: unknown[] = [];\n const valuesClauses: string[] = [];\n\n for (const record of mappedRecords) {\n const placeholders: string[] = [];\n for (const col of columns) {\n allValues.push(record[col]);\n placeholders.push(\"?\");\n }\n valuesClauses.push(`(${placeholders.join(\", \")})`);\n }\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES ${valuesClauses.join(\", \")}`;\n\n const [result] = await connection.query<ResultSetHeader>(\n sql,\n allValues\n );\n\n // For bulk insert, we need to SELECT the inserted rows\n // MySQL's insertId gives the first auto-increment ID\n if (!skipReread && result.insertId && result.affectedRows > 0) {\n const ids: number[] = [];\n for (let i = 0; i < result.affectedRows; i++) {\n ids.push(result.insertId + i);\n }\n const placeholders = ids.map(() => \"?\").join(\", \");\n const bulkAliases = this.dateWallClockAliases(tableObj, \"*\");\n const bulkSpelled = bulkAliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT *${bulkSpelled ? `, ${bulkSpelled}` : \"\"} FROM ${this.escapeIdentifier(table)} WHERE id IN (${placeholders})`,\n ids\n );\n return (rows as T[]).map(r =>\n this.mapRowFromRawSql(tableObj, r, bulkAliases)\n );\n }\n\n // Fallback: return empty if we can't determine inserted rows\n return [];\n },\n\n // Adapter-built, as `insert` above is; `transactionUpdate` says why.\n // A column the model does not declare binds natively, as every value\n // on this path's insert does.\n update: this.transactionUpdate(\n txDb,\n async statement => {\n // No RETURNING on MySQL: nothing to report, the read-back is by\n // the caller's predicate.\n await txDb().execute(statement);\n return undefined;\n },\n // Structured values as JSON text: mysql2's formatter spells an object\n // as `key = value` pairs and an array as a value list, neither JSON.\n value => this.bindUnmodeledStructuredAsJson(value)\n ),\n\n ...this.createTransactionForwarders(txDb),\n\n // Savepoints disabled per approved approach\n savepoint: undefined,\n rollbackToSavepoint: undefined,\n releaseSavepoint: undefined,\n };\n }\n\n /**\n * Classifies a MySQL error into a DatabaseError.\n *\n * @param error - Original error from mysql2\n * @param sql - SQL statement that caused the error (optional)\n * @returns DatabaseError with proper classification\n */\n protected override classifyError(\n error: unknown,\n sql?: string\n ): DatabaseError {\n // Why short-circuit on existing DatabaseError: F17's\n // UnsupportedDialectVersionError is already a typed DatabaseError with\n // kind: \"unsupported_version\" plus detectedVersion/requiredVersion\n // fields. Re-wrapping it here would erase those fields and re-tag it\n // as kind: \"unknown\".\n if (isDatabaseError(error)) return error;\n\n // Classified from mysql2's own error, which Drizzle puts on `cause` when it\n // wraps a failed query (`driverErrorIn`); it is the one carrying `errno`.\n const mysqlError = this.driverErrorIn<{\n errno?: number;\n code?: string;\n sqlState?: string;\n message?: string;\n sql?: string;\n cause?: unknown;\n }>(error, candidate => typeof candidate.errno === \"number\");\n\n // Determine error kind from MySQL error number\n const kind: DatabaseErrorKind =\n (mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno]) || \"unknown\";\n\n // Build error message\n let message = mysqlError.message ?? String(error);\n if (sql && kind === \"query\") {\n message = `Query failed: ${message}`;\n }\n\n return createDatabaseError({\n kind,\n message,\n code: mysqlError.code ?? mysqlError.errno?.toString(),\n detail: mysqlError.sql,\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\n/** A table's keys by database column name, as its model declares them. */\ninterface TableKeys {\n primary: string[];\n unique: { name: string; columns: string[] }[];\n}\n\nconst TABLE_KEYS = new WeakMap<MySqlTable, TableKeys>();\n\n/**\n * A table's primary key and its unique keys other than the primary key, read\n * from its model once and kept for the table's lifetime: a model is not\n * changed in place, a new schema brings new table objects.\n */\nfunction keysOf(tableObj: MySqlTable): TableKeys {\n const cached = TABLE_KEYS.get(tableObj);\n if (cached) return cached;\n const config = getTableConfig(tableObj);\n const primary = [\n ...config.columns.filter(column => column.primary),\n ...config.primaryKeys.flatMap(key => key.columns),\n ].map(column => column.name);\n const unique = [\n ...config.columns\n .filter(column => column.isUnique && !column.primary)\n .map(column => ({\n // Drizzle's own name for an unnamed column-level unique key.\n name: column.uniqueName ?? `${config.name}_${column.name}_unique`,\n columns: [column.name],\n })),\n ...config.uniqueConstraints.map(constraint => ({\n name: constraint.name,\n columns: constraint.columns.map(column => column.name),\n })),\n ...config.indexes\n .filter(index => index.config.unique === true)\n .map(index => ({\n name: index.config.name,\n columns: (index.config.columns as { name: string }[]).map(\n column => column.name\n ),\n })),\n ];\n const keys = { primary, unique };\n TABLE_KEYS.set(tableObj, keys);\n return keys;\n}\n\n/**\n * Create a MySQL database adapter.\n *\n * @param config - MySQL adapter configuration\n * @returns A new MySqlAdapter instance\n *\n * @example\n * ```typescript\n * // Simple usage with URL\n * const adapter = createMySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * });\n *\n * // Full configuration\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * pool: {\n * min: 2,\n * max: 20,\n * idleTimeoutMs: 30000,\n * connectionTimeoutMs: 10000,\n * },\n * ssl: {\n * rejectUnauthorized: true,\n * },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport function createMySqlAdapter(config: MySqlAdapterConfig): MySqlAdapter {\n return new MySqlAdapter(config);\n}\n\n/**\n * Type guard to check if a value is a MySqlAdapter.\n *\n * @param value - Value to check\n * @returns True if value is a MySqlAdapter instance\n *\n * @example\n * ```typescript\n * if (isMySqlAdapter(adapter)) {\n * // TypeScript knows adapter is MySqlAdapter\n * console.log('Using MySQL');\n * }\n * ```\n */\nexport function isMySqlAdapter(value: unknown): value is MySqlAdapter {\n return value instanceof MySqlAdapter;\n}\n"],"mappings":"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"}
|
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, SessionOptions, SqlParam, SslConfig, StatementSession, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
|
|
2
|
+
import { AdapterLogger, BaseAdapterConfig, ConnectionInspection, 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
|
/**
|
|
@@ -100,6 +100,28 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
100
100
|
* - _connectionQueue: waiting requests
|
|
101
101
|
*/
|
|
102
102
|
getPoolStats(): PoolStats | null;
|
|
103
|
+
/**
|
|
104
|
+
* The connection as a health check reads it, on one pooled connection: the
|
|
105
|
+
* server's version, timed for the round trip, then that session's
|
|
106
|
+
* `Ssl_cipher` status, which names the cipher when the session's connection
|
|
107
|
+
* is encrypted and is empty when it is not.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* The status and the connection's own socket are weighed by
|
|
111
|
+
* `encryptionOf`: behind a proxy the status can describe the proxy's own
|
|
112
|
+
* connection to the server, while this connection's socket is TLS. The
|
|
113
|
+
* socket is read as it is, never inferred from the `ssl` setting.
|
|
114
|
+
*
|
|
115
|
+
* Whether the database is on this machine is judged from the configured
|
|
116
|
+
* address, which never leaves this method. Any failure answers `"unknown"`.
|
|
117
|
+
*/
|
|
118
|
+
override inspectConnection(): Promise<ConnectionInspection>;
|
|
119
|
+
/**
|
|
120
|
+
* The host this adapter connects to, as mysql2 resolves it: the connection
|
|
121
|
+
* string's when one is set, else the `host` option, else `localhost`.
|
|
122
|
+
* `undefined` when the connection string cannot be read.
|
|
123
|
+
*/
|
|
124
|
+
private connectionHost;
|
|
103
125
|
/**
|
|
104
126
|
* Execute a raw SQL query.
|
|
105
127
|
*
|
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, SessionOptions, SqlParam, SslConfig, StatementSession, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
|
|
2
|
+
import { AdapterLogger, BaseAdapterConfig, ConnectionInspection, 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
|
/**
|
|
@@ -100,6 +100,28 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
100
100
|
* - _connectionQueue: waiting requests
|
|
101
101
|
*/
|
|
102
102
|
getPoolStats(): PoolStats | null;
|
|
103
|
+
/**
|
|
104
|
+
* The connection as a health check reads it, on one pooled connection: the
|
|
105
|
+
* server's version, timed for the round trip, then that session's
|
|
106
|
+
* `Ssl_cipher` status, which names the cipher when the session's connection
|
|
107
|
+
* is encrypted and is empty when it is not.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* The status and the connection's own socket are weighed by
|
|
111
|
+
* `encryptionOf`: behind a proxy the status can describe the proxy's own
|
|
112
|
+
* connection to the server, while this connection's socket is TLS. The
|
|
113
|
+
* socket is read as it is, never inferred from the `ssl` setting.
|
|
114
|
+
*
|
|
115
|
+
* Whether the database is on this machine is judged from the configured
|
|
116
|
+
* address, which never leaves this method. Any failure answers `"unknown"`.
|
|
117
|
+
*/
|
|
118
|
+
override inspectConnection(): Promise<ConnectionInspection>;
|
|
119
|
+
/**
|
|
120
|
+
* The host this adapter connects to, as mysql2 resolves it: the connection
|
|
121
|
+
* string's when one is set, else the `host` option, else `localhost`.
|
|
122
|
+
* `undefined` when the connection string cannot be read.
|
|
123
|
+
*/
|
|
124
|
+
private connectionHost;
|
|
103
125
|
/**
|
|
104
126
|
* Execute a raw SQL query.
|
|
105
127
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -379,6 +379,59 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
381
|
/**
|
|
382
|
+
* The connection as a health check reads it, on one pooled connection: the
|
|
383
|
+
* server's version, timed for the round trip, then that session's
|
|
384
|
+
* `Ssl_cipher` status, which names the cipher when the session's connection
|
|
385
|
+
* is encrypted and is empty when it is not.
|
|
386
|
+
*
|
|
387
|
+
* @remarks
|
|
388
|
+
* The status and the connection's own socket are weighed by
|
|
389
|
+
* `encryptionOf`: behind a proxy the status can describe the proxy's own
|
|
390
|
+
* connection to the server, while this connection's socket is TLS. The
|
|
391
|
+
* socket is read as it is, never inferred from the `ssl` setting.
|
|
392
|
+
*
|
|
393
|
+
* Whether the database is on this machine is judged from the configured
|
|
394
|
+
* address, which never leaves this method. Any failure answers `"unknown"`.
|
|
395
|
+
*/
|
|
396
|
+
async inspectConnection() {
|
|
397
|
+
const local = this.isLocalHost(this.connectionHost());
|
|
398
|
+
let connection;
|
|
399
|
+
try {
|
|
400
|
+
connection = await this.ensurePool().getConnection();
|
|
401
|
+
const started = performance.now();
|
|
402
|
+
const [versionRows] = await connection.query("SELECT VERSION() AS version");
|
|
403
|
+
const roundTripMs = Math.max(0, Math.round(performance.now() - started));
|
|
404
|
+
const [statusRows] = await connection.query("SHOW SESSION STATUS LIKE 'Ssl_cipher'");
|
|
405
|
+
const cipher = statusRows[0]?.Value;
|
|
406
|
+
const version = versionRows[0]?.version;
|
|
407
|
+
return {
|
|
408
|
+
encryption: this.encryptionOf({
|
|
409
|
+
session: typeof cipher === "string" ? cipher !== "" : void 0,
|
|
410
|
+
ownSocketTls: this.ownSocketIsTls(connection)
|
|
411
|
+
}),
|
|
412
|
+
local,
|
|
413
|
+
roundTripMs,
|
|
414
|
+
...typeof version === "string" && version !== "" ? { serverVersion: version.split("-")[0] } : {}
|
|
415
|
+
};
|
|
416
|
+
} catch {
|
|
417
|
+
return {
|
|
418
|
+
encryption: "unknown",
|
|
419
|
+
local
|
|
420
|
+
};
|
|
421
|
+
} finally {
|
|
422
|
+
connection?.release();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The host this adapter connects to, as mysql2 resolves it: the connection
|
|
427
|
+
* string's when one is set, else the `host` option, else `localhost`.
|
|
428
|
+
* `undefined` when the connection string cannot be read.
|
|
429
|
+
*/
|
|
430
|
+
connectionHost() {
|
|
431
|
+
const configured = this.config.url ? this.hostOfUrl(this.config.url) : this.config.host ?? "";
|
|
432
|
+
return configured === "" ? "localhost" : configured;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
382
435
|
* Execute a raw SQL query.
|
|
383
436
|
*
|
|
384
437
|
* @param sql - SQL query string with ? placeholders
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/pool-wait.ts","../src/index.ts"],"sourcesContent":["/**\n * The bounded wait for a pooled connection.\n *\n * mysql2 has no such bound. A request that finds every connection in use is\n * queued until one is released, however long that takes, so a database that\n * stays busy holds every request behind it indefinitely. This bounds the wait\n * the way pg-pool's `connectionTimeoutMillis` does: past the bound, the\n * request fails with the adapters' own pool-wait timeout error, which core\n * answers with \"database busy\".\n *\n * Only the wait in the queue: a request that opens a new connection is bounded\n * by mysql2's `connectTimeout` instead, set from the same setting, and a\n * connection that cannot be opened is a database that cannot be reached\n * rather than one that is busy.\n */\nimport { poolWaitTimeoutError } from \"@nextlyhq/adapter-drizzle/types\";\n\n/** A connection the pool hands out, as far as the bound needs one. */\ninterface QueuedConnection {\n release(): void;\n}\n\ntype GetConnectionCallback = (\n err: Error | null,\n connection?: QueuedConnection\n) => void;\n\n/**\n * The part of mysql2's callback pool the bound uses. Declared here because\n * mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\nexport interface QueueingPool {\n getConnection(callback: GetConnectionCallback): void;\n once(event: \"enqueue\", listener: () => void): unknown;\n removeListener(event: \"enqueue\", listener: () => void): unknown;\n /** mysql2's queue of waiting requests (a Denque). */\n _connectionQueue?: {\n length: number;\n peekAt(index: number): unknown;\n removeOne(index: number): unknown;\n };\n}\n\n/**\n * Takes a request that gave up out of the pool's queue, so a pool that stays\n * saturated does not collect every request that has already failed, and a\n * released connection goes to one still waiting. Returns whether it was\n * there: with diagnostics tracing on, mysql2 queues a wrapper of the\n * callback rather than the callback itself, and then it stays queued until a\n * connection reaches it.\n */\nfunction leaveQueue(pool: QueueingPool, waiting: GetConnectionCallback) {\n const queue = pool._connectionQueue;\n if (!queue) return false;\n for (let at = 0; at < queue.length; at++) {\n if (queue.peekAt(at) === waiting) {\n queue.removeOne(at);\n return true;\n }\n }\n return false;\n}\n\n/**\n * Makes `pool` fail a request that has waited `boundMs` in its queue for a\n * connection, and returns it. A bound of 0 leaves the wait unbounded, as\n * pg-pool reads a `connectionTimeoutMillis` of 0.\n *\n * Replaces the pool's own `getConnection`, which its `query` and `execute`\n * go through, so every path to a connection is bounded: the adapter's\n * statements, its transactions, and the Drizzle handle core services query\n * through.\n *\n * A request that gives up leaves the queue. Where mysql2 cannot be asked to\n * drop it, the connection it is eventually handed is released straight back\n * rather than held by a request that has already failed.\n */\nexport function boundPoolWait<P extends QueueingPool>(\n pool: P,\n boundMs: number\n): P {\n if (boundMs <= 0) return pool;\n const getConnection = pool.getConnection.bind(pool);\n const bounded = (callback: GetConnectionCallback) => {\n let answered = false;\n // Set only once the request has queued; read by the answer that ends it.\n const wait: { timer?: ReturnType<typeof setTimeout> } = {};\n // mysql2 emits `enqueue` synchronously, inside this call, exactly when\n // the request joins the queue rather than taking or opening a connection.\n let queued = false;\n const onEnqueue = () => (queued = true);\n pool.once(\"enqueue\", onEnqueue);\n const waiting: GetConnectionCallback = (err, connection) => {\n if (answered) {\n connection?.release();\n return;\n }\n answered = true;\n clearTimeout(wait.timer);\n callback(err, connection);\n };\n getConnection(waiting);\n pool.removeListener(\"enqueue\", onEnqueue);\n if (!queued || answered) return;\n wait.timer = setTimeout(() => {\n answered = true;\n leaveQueue(pool, waiting);\n callback(poolWaitTimeoutError(boundMs));\n }, boundMs);\n };\n pool.getConnection = bounded;\n return pool;\n}\n","/**\n * @nextlyhq/adapter-mysql\n *\n * MySQL database adapter for Nextly.\n * Extends DrizzleAdapter from @nextlyhq/adapter-drizzle to provide MySQL-specific functionality.\n *\n * @remarks\n * This adapter uses the mysql2 package for database connectivity and provides:\n * - Connection pooling via mysql2 Pool\n * - Full transaction support with isolation levels\n * - CRUD operations with workarounds for missing RETURNING clause\n * - MySQL-specific error classification\n * - Automatic retry for deadlocks (error 1213)\n *\n * @example\n * ```typescript\n * import { createMySqlAdapter } from '@nextlyhq/adapter-mysql';\n *\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * });\n *\n * await adapter.connect();\n *\n * // Query data\n * const users = await adapter.select('users', {\n * where: { and: [{ column: 'status', op: '=', value: 'active' }] },\n * limit: 10,\n * });\n *\n * await adapter.disconnect();\n * ```\n *\n * @packageDocumentation\n */\n\nimport { DrizzleAdapter } from \"@nextlyhq/adapter-drizzle\";\n// F17: connect-time DB version check shared across all adapters.\nimport {\n createDatabaseError,\n isDatabaseError,\n type MySqlAdapterConfig,\n type DatabaseCapabilities,\n type PoolStats,\n type TransactionContext,\n type TransactionOptions,\n type SessionOptions,\n type StatementSession,\n type SqlParam,\n type WhereClause,\n type WhereCondition,\n type WhereOperator,\n type SelectOptions,\n type InsertOptions,\n type UpdateOptions,\n type DeleteOptions,\n type UpsertOptions,\n type OrderBySpec,\n type JoinSpec,\n type DatabaseError,\n type DatabaseErrorKind,\n type BaseAdapterConfig,\n type AdapterLogger,\n type PoolConfig,\n type SslConfig,\n} from \"@nextlyhq/adapter-drizzle/types\";\nimport {\n acceptsForShare,\n checkDialectVersion,\n} from \"@nextlyhq/adapter-drizzle/version-check\";\nimport { and, eq, getColumns } from \"drizzle-orm\";\nimport type { AnyRelations, Column, SQL } from \"drizzle-orm\";\nimport { getTableConfig, type MySqlTable } from \"drizzle-orm/mysql-core\";\nimport { drizzle, type MySql2Database } from \"drizzle-orm/mysql2\";\nimport type {\n Pool as CallbackPool,\n Connection as CallbackConnection,\n} from \"mysql2\";\nimport mysql from \"mysql2/promise\";\nimport type {\n ConnectionOptions,\n PoolOptions,\n RowDataPacket,\n ResultSetHeader,\n} from \"mysql2/promise\";\n\nimport { boundPoolWait, type QueueingPool } from \"./pool-wait\";\n\n// mysql2 type definitions use mixin patterns that TypeScript struggles with.\n// We define explicit interfaces for the methods we need.\n\n/**\n * Query result type - either rows or a result header\n */\ntype QueryResult = RowDataPacket[] | RowDataPacket[][] | ResultSetHeader;\n\n/**\n * Queryable interface for mysql2 connections\n */\ninterface Queryable {\n query<T extends QueryResult>(sql: string): Promise<[T, unknown]>;\n query<T extends QueryResult>(\n sql: string,\n values: unknown[]\n ): Promise<[T, unknown]>;\n}\n\n/**\n * mysql2 Pool interface with query method\n */\ninterface Pool extends Queryable {\n getConnection(): Promise<PoolConnection>;\n end(): Promise<void>;\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n}\n\n/**\n * A mysql2 connection of its own, outside the pool\n */\ninterface OwnConnection extends Queryable {\n /** Closes the connection. */\n destroy(): void;\n}\n\n/**\n * mysql2 PoolConnection interface with query and release methods\n */\ninterface PoolConnection extends Queryable {\n /** Returns the connection to the pool, for the next request to use. */\n release(): void;\n /** Closes the connection and removes it from the pool. */\n destroy(): void;\n}\n\n/**\n * What a transaction changed on its connection's session, to put back before\n * the connection returns to the pool; null when it changed nothing.\n *\n * MySQL has no transaction-scoped form of `innodb_lock_wait_timeout` or\n * `sql_mode`: a `SET SESSION` outlives the transaction, and the pooled\n * connection would carry one transaction's setting into every later query\n * handed it. Each field present is the session's value from before the\n * change, or null when it could not be read, and then the connection cannot\n * be put back.\n */\ntype SessionRestore = {\n lockWaitTimeout?: number | null;\n sqlMode?: string | null;\n} | null;\n\n/**\n * Puts back what a transaction changed on its connection's session. False\n * when that could not be done, and the connection must not be pooled.\n */\nasync function restoreSession(\n connection: PoolConnection,\n restore: SessionRestore\n): Promise<boolean> {\n if (restore === null) return true;\n if (restore.lockWaitTimeout === null || restore.sqlMode === null) {\n return false;\n }\n try {\n if (restore.lockWaitTimeout !== undefined) {\n // An integer read from the server, never caller input.\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`\n );\n }\n if (restore.sqlMode !== undefined) {\n // Read from the server, and bound as a value all the same.\n await connection.query(\"SET SESSION sql_mode = ?\", [restore.sqlMode]);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ends a transaction's hold on its connection: back to the pool once its\n * session is as the transaction found it, or closed when it is not. A\n * connection whose ROLLBACK failed (`rollbackFailed`) may still be inside\n * the transaction, and the next request handed it would write into a\n * transaction nobody commits.\n */\nasync function returnTransactionConnection(\n connection: PoolConnection,\n rollbackFailed: boolean,\n restore: SessionRestore\n): Promise<void> {\n if (!rollbackFailed && (await restoreSession(connection, restore))) {\n connection.release();\n } else {\n connection.destroy();\n }\n}\n\n/**\n * A newly opened pooled connection, as far as the time-zone pin needs one.\n *\n * The callback form of `query` is load-bearing twice: a pin that failed with\n * nothing handling the error would end up an unhandled `error` event on the\n * connection, and a failure swallowed without this `destroy` would leave an\n * unpinned session in the pool to serve shifted values.\n */\ninterface PinnableConnection {\n query(sql: string, callback: (error: Error | null) => void): void;\n destroy(): void;\n}\n\n/**\n * The part of mysql2's callback pool the time-zone pin uses. Declared here\n * because mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\ninterface PinnablePool {\n on(\n event: \"connection\",\n listener: (connection: PinnableConnection) => void\n ): unknown;\n}\n\n/**\n * mysql2 accepts only `local`, `Z` and `±HH:MM` spellings for its driver\n * zone, and the ones that resolve to UTC are exactly these. `local` resolves\n * to the Node process's own zone, so it agrees with a UTC session only on a\n * UTC host.\n */\nfunction isUtcDriverZone(zone: string): boolean {\n if (zone === \"local\") return new Date().getTimezoneOffset() === 0;\n return zone === \"Z\" || zone === \"+00:00\" || zone === \"-00:00\";\n}\n\n// Re-export types for convenience\nexport type {\n MySqlAdapterConfig,\n DatabaseCapabilities,\n PoolStats,\n TransactionContext,\n TransactionOptions,\n SqlParam,\n WhereClause,\n WhereCondition,\n WhereOperator,\n SelectOptions,\n InsertOptions,\n UpdateOptions,\n DeleteOptions,\n UpsertOptions,\n OrderBySpec,\n JoinSpec,\n DatabaseError,\n DatabaseErrorKind,\n BaseAdapterConfig,\n AdapterLogger,\n PoolConfig,\n SslConfig,\n};\n\n/**\n * Package version\n */\nexport const VERSION = \"0.1.0\";\n\n/**\n * Default pool configuration values.\n */\nconst DEFAULT_POOL_CONFIG = {\n min: 2,\n max: 10,\n idleTimeoutMs: 30000,\n // How long a request waits for a connection, in the queue or opening one:\n // the same bound, and the same default, as the PostgreSQL adapter's.\n connectionTimeoutMs: 15000,\n};\n\n/**\n * MySQL error codes mapping to DatabaseErrorKind.\n *\n * @see https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html\n */\nconst MYSQL_ERROR_CODES: Record<number, DatabaseErrorKind> = {\n // Unique/Duplicate key violations\n 1022: \"unique_violation\", // ER_DUP_KEY\n 1062: \"unique_violation\", // ER_DUP_ENTRY\n 1169: \"unique_violation\", // ER_DUP_UNIQUE\n 1586: \"unique_violation\", // ER_DUP_ENTRY_WITH_KEY_NAME\n\n // Foreign key violations\n 1216: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW\n 1217: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED\n 1451: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED_2\n 1452: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW_2\n\n // Not null violations\n 1048: \"not_null_violation\", // ER_BAD_NULL_ERROR\n 1364: \"not_null_violation\", // ER_NO_DEFAULT_FOR_FIELD\n\n // Check constraint violations (MySQL 8.0.16+)\n 3819: \"check_violation\", // ER_CHECK_CONSTRAINT_VIOLATED\n\n // Deadlock\n 1213: \"deadlock\", // ER_LOCK_DEADLOCK\n\n // Timeout\n 1205: \"timeout\", // ER_LOCK_WAIT_TIMEOUT\n\n // Connection errors\n 1040: \"connection\", // ER_CON_COUNT_ERROR - Too many connections\n 1042: \"connection\", // ER_BAD_HOST_ERROR\n 1043: \"connection\", // ER_HANDSHAKE_ERROR\n 1044: \"connection\", // ER_DBACCESS_DENIED_ERROR\n 1045: \"connection\", // ER_ACCESS_DENIED_ERROR\n 1129: \"connection\", // ER_HOST_IS_BLOCKED\n 1130: \"connection\", // ER_HOST_NOT_PRIVILEGED\n 2002: \"connection\", // CR_CONNECTION_ERROR\n 2003: \"connection\", // CR_CONN_HOST_ERROR\n 2006: \"connection\", // CR_SERVER_GONE_ERROR\n 2013: \"connection\", // CR_SERVER_LOST\n\n // Query errors\n 1064: \"query\", // ER_PARSE_ERROR\n 1146: \"query\", // ER_NO_SUCH_TABLE\n 1054: \"query\", // ER_BAD_FIELD_ERROR\n};\n\n/** ER_LOCK_DEADLOCK: the transaction lost a race, and running it again may succeed. */\nconst DEADLOCK = 1213;\n\n/**\n * The MySQL error number an error carries: its own, or that of the driver\n * error it wraps, as a classified or a Drizzle query error does.\n */\nfunction errnoOf(error: unknown): number | undefined {\n for (let at: unknown = error, hops = 0; hops < 4; hops++) {\n if (typeof at !== \"object\" || at === null) return undefined;\n const errno: unknown = Reflect.get(at, \"errno\");\n if (typeof errno === \"number\") return errno;\n at = Reflect.get(at, \"cause\");\n }\n return undefined;\n}\n\n/**\n * MySQL database adapter for Nextly.\n *\n * Extends the base DrizzleAdapter to provide MySQL-specific functionality\n * using the mysql2 package.\n *\n * @remarks\n * MySQL has some limitations compared to PostgreSQL:\n * - No native RETURNING clause (requires INSERT then SELECT)\n * - No native ILIKE (uses LOWER() LIKE workaround)\n * - No native JSONB (uses JSON type)\n * - No array types\n * - Savepoints disabled for safety (MySQL has nested transaction quirks)\n *\n * @example\n * ```typescript\n * const adapter = new MySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * pool: { max: 20 },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport class MySqlAdapter extends DrizzleAdapter {\n // getDrizzle memoization: drizzle v1's constructor builds a relational\n // query builder per table in the relations config (~40 tables), and the\n // service layer resolves an instance on every db access — construct once\n // per relations object (identity-stable: the schema registry caches it\n // and hands out a NEW object on invalidation, which naturally misses\n // this cache and produces a fresh instance).\n private drizzleByRelations = new WeakMap<AnyRelations, unknown>();\n private drizzleBare: unknown;\n\n /**\n * The database dialect - always 'mysql' for this adapter.\n */\n readonly dialect = \"mysql\" as const;\n\n /**\n * Adapter configuration.\n */\n protected readonly config: MySqlAdapterConfig;\n\n /**\n * Connection pool instance.\n */\n private pool: Pool | null = null;\n\n /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */\n private readonly sessions = new Set<Promise<unknown>>();\n\n /**\n * Connection state flag.\n */\n private connected = false;\n\n /**\n * Whether the server accepts `SELECT ... FOR SHARE`, read from its version\n * string at connect. Undefined until then, which callers treat as not known.\n */\n private sharedRowLock: boolean | undefined;\n private functionalIndexes: boolean | undefined;\n\n /**\n * Creates a new MySQL adapter instance.\n *\n * @param config - Adapter configuration\n */\n constructor(config: MySqlAdapterConfig) {\n super();\n this.config = config;\n }\n\n /**\n * Connect to the MySQL database.\n * Creates a connection pool using mysql2.\n *\n * @remarks\n * This method initializes the connection pool and verifies connectivity\n * by executing a simple query. It is idempotent - calling it multiple\n * times will not create multiple pools.\n *\n * @throws {DatabaseError} If connection fails\n */\n async connect(): Promise<void> {\n if (this.connected && this.pool) {\n return;\n }\n\n try {\n // Warned once, here, rather than on every session configuration the\n // standalone connections build: the configuration cannot change\n // between calls.\n this.warnOnDriverZoneDisagreement();\n const poolConfig = this.buildPoolConfig();\n const pool = mysql.createPool(poolConfig);\n // Pin the session time zone the moment each pooled connection opens.\n // Left unpinned, a session serves in whatever zone the server starts it\n // in — `SYSTEM` on the containers, anything on a self-hosted server —\n // and writes, window bounds and session-zone computations all follow\n // it. mysql2 emits `connection` when a physical connection is\n // established and BEFORE it reaches any caller, and each connection\n // queues its commands in order, so this SET has always run by the time\n // the first query handed the connection is sent; a replaced connection\n // is pinned again by the same listener.\n (pool.pool as unknown as PinnablePool).on(\"connection\", connection => {\n connection.query(\"SET SESSION time_zone = '+00:00'\", error => {\n // A session that could not be pinned must never be pooled: it\n // would serve in whatever zone it answers in.\n if (error) connection.destroy();\n });\n });\n // mysql2 queues a request for a connection without limit; it fails as\n // busy past the same bound a new connection gets to open.\n boundPoolWait(\n pool.pool as unknown as QueueingPool,\n poolConfig.connectTimeout ?? 0\n );\n // Cast to our Pool interface - mysql2's mixin types don't resolve properly\n this.pool = pool as unknown as Pool;\n\n // Verify connection with smoke test, then check dialect version.\n // Why: F17 hard-fails at connect on real MySQL <8.0 (no variant\n // token detected). Recognized variants (MariaDB, TiDB, Aurora,\n // PlanetScale, Vitess) log a warning via the adapter logger and\n // proceed. Truly unparseable strings hard-fail so users see the\n // issue at boot rather than mid-apply.\n const connection = await this.pool.getConnection();\n try {\n await connection.query(\"SELECT 1\");\n const { variant, functionalIndexes } = await checkDialectVersion(\n connection,\n \"mysql\",\n {\n // Why: route variant warnings through the adapter's logger so\n // users see a single, consistent log surface.\n onWarning: msg => this.config.logger?.warn?.(msg),\n }\n );\n // MariaDB and TiDB connect through this adapter too, and reject\n // `FOR SHARE`; the capability tells callers which lock they can take.\n this.sharedRowLock = acceptsForShare(variant);\n // Whether a unique index may key on an expression, which per-language uniqueness of a\n // translated value does.\n this.functionalIndexes = functionalIndexes;\n this.connected = true;\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection established\", {\n host: this.config.host ?? \"from URL\",\n database: this.config.database ?? \"from URL\",\n });\n }\n } finally {\n connection.release();\n }\n } catch (error) {\n // Clean up on failure\n if (this.pool) {\n await this.pool.end().catch(() => {});\n this.pool = null;\n }\n throw this.classifyError(error);\n }\n }\n\n /**\n * Disconnect from the MySQL database.\n * Gracefully closes the connection pool.\n *\n * @remarks\n * This method is idempotent - calling it multiple times is safe.\n * It waits for all connections to be released before shutting down,\n * including each running `withSession`'s connection of its own.\n */\n async disconnect(): Promise<void> {\n // Detach the pool FIRST, then drop the memoized drizzle instances —\n // this closes the repopulation window where a concurrent getDrizzle()\n // call during the (async) pool.end() could cache an instance wrapping\n // the closing pool.\n const pool = this.pool;\n this.pool = null;\n this.drizzleBare = undefined;\n this.drizzleByRelations = new WeakMap();\n if (!pool) {\n return;\n }\n\n try {\n // With the pool detached no session starts; those running end first.\n await Promise.allSettled([...this.sessions]);\n await pool.end();\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection closed\");\n }\n } finally {\n this.connected = false;\n }\n }\n\n /**\n * Check if connected to the database.\n */\n isConnected(): boolean {\n return this.connected && this.pool !== null;\n }\n\n /**\n * Get connection pool statistics.\n * Returns null if not connected.\n *\n * @remarks\n * MySQL2 pool exposes different stats than pg:\n * - _allConnections: all connections\n * - _freeConnections: idle connections\n * - _connectionQueue: waiting requests\n */\n getPoolStats(): PoolStats | null {\n if (!this.pool) {\n return null;\n }\n\n // mysql2 Pool internal properties (cast to access internals)\n const poolInternal = this.pool as unknown as {\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n };\n\n const internal = poolInternal.pool;\n if (!internal) {\n return {\n total: 0,\n idle: 0,\n waiting: 0,\n active: 0,\n };\n }\n\n const total = internal._allConnections?.length ?? 0;\n const idle = internal._freeConnections?.length ?? 0;\n const waiting = internal._connectionQueue?.length ?? 0;\n\n return {\n total,\n idle,\n waiting,\n active: total - idle,\n };\n }\n\n /**\n * Execute a raw SQL query.\n *\n * @param sql - SQL query string with ? placeholders\n * @param params - Query parameters\n * @returns Query results\n *\n * @throws {DatabaseError} If query execution fails\n */\n async executeQuery<T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> {\n const pool = this.ensurePool();\n const startTime = Date.now();\n\n try {\n // Outside the calling flow's transaction, if it has one (`notePoolAccess`).\n this.notePoolAccess(sql);\n const [rows] = await pool.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n\n // Log query if logger configured\n if (this.config.logger?.query) {\n const durationMs = Date.now() - startTime;\n this.config.logger.query(sql, params, durationMs);\n }\n\n return rows as T[];\n } catch (error) {\n throw this.classifyError(error, sql);\n }\n }\n\n /**\n * Execute work within a transaction.\n *\n * @param work - Function containing transactional operations\n * @param options - Transaction options (isolation level, timeout, retry)\n * @returns Result of the work function\n *\n * @remarks\n * MySQL transactions support isolation levels. Automatic retry is\n * implemented for deadlocks (error 1213) when `retryCount` is specified.\n *\n * Note: Savepoints are disabled in this adapter for safety due to\n * MySQL's quirks with nested transactions.\n *\n * A call made inside another transaction's work therefore joins it flat,\n * on its connection: its statements are the outer's. Its failure cannot be\n * undone alone, so it fails the whole transaction, even when the outer\n * catches it: the outer's COMMIT is refused, it rolls back, and it rejects\n * with the error that failed the joined call; any later joined call fails\n * with that error at once. A joined call keeps the outer's options and is\n * never retried itself. `retryWhen` is asked first, of every error that\n * ends an attempt, application ones included.\n */\n transaction<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options?: TransactionOptions\n ): Promise<T> {\n return this.openOrJoin(work, options, {\n run: (_attempt, openedAt) => this.runAttempt(work, options, openedAt),\n retryable: error => errnoOf(error) === DEADLOCK,\n describe: error => {\n const errno = errnoOf(error);\n return {\n label: errno === DEADLOCK ? \"deadlock\" : \"a retryable error\",\n detail: { errno },\n };\n },\n logger: this.config.logger,\n });\n }\n\n /**\n * Runs `work` on a connection of its own, opened for it with the pool's\n * connection settings, outside a transaction, each statement committing as\n * it runs.\n *\n * @remarks\n * Not a pooled connection: on a server whose sessions start with autocommit\n * off, one can hold a transaction an earlier caller left open, and turning\n * autocommit on would commit that caller's writes. A new connection holds\n * nothing. It does not count against `pool.max`.\n *\n * Each statement commits as it runs, whatever the server's own autocommit\n * default. With `strictSqlMode` the session runs in strict mode, as a\n * transaction given the same option does, so a statement that would cut a\n * value short fails and changes nothing. The connection is closed\n * afterwards, whether `work` finished or threw, so whatever `work` set on\n * its session (a temporary table, a session variable, a lock) ends with it.\n */\n override async withSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n // A precondition: before any connection is opened.\n this.refuseSessionInTransaction();\n // Connected first, as every other call requires.\n this.ensurePool();\n // Tracked before its connection opens: `disconnect` waits for it.\n const running = this.runSession(work, options);\n this.sessions.add(running);\n try {\n return await running;\n } finally {\n this.sessions.delete(running);\n }\n }\n\n /** `withSession`'s work, on a connection it opens and closes. */\n private async runSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n let connection: OwnConnection;\n try {\n // mysql2's connection already satisfies `OwnConnection`, so it is assigned as it comes.\n connection = await mysql.createConnection(this.buildConnectionConfig());\n } catch (error) {\n throw this.classifyError(error);\n }\n try {\n try {\n // A server whose sessions start with autocommit off would otherwise\n // hold every statement in a transaction nobody commits.\n await connection.query(\"SET SESSION autocommit = 1\");\n // The connection opened outside the pool, so the pool's `connection`\n // listener never pins it; its driver already parses in UTC, and a\n // session still in the server's zone would shift every value a\n // round-trip fails to cancel. The work below never runs unpinned.\n await connection.query(\"SET SESSION time_zone = '+00:00'\");\n if (options?.strictSqlMode) {\n // Nothing to put back: the connection is closed afterwards.\n await this.enterStrictMode(connection, {});\n }\n } catch (error) {\n throw this.classifyError(error);\n }\n return await work({\n execute: async <R = unknown>(\n statement: string,\n params: SqlParam[] = []\n ): Promise<R[]> => {\n const startTime = Date.now();\n try {\n const [rows] = await connection.query<RowDataPacket[]>(\n statement,\n params as unknown[]\n );\n if (this.config.logger?.query) {\n this.config.logger.query(\n statement,\n params,\n Date.now() - startTime\n );\n }\n return rows as R[];\n } catch (error) {\n throw this.classifyError(error, statement);\n }\n },\n });\n } finally {\n connection.destroy();\n }\n }\n\n /** One attempt at an outermost transaction, on a connection of its own. */\n private async runAttempt<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options: TransactionOptions | undefined,\n openedAt: string | undefined\n ) {\n const pool = this.ensurePool();\n // A checkout, taken from the pool like `executeQuery`'s queries are.\n this.notePoolAccess();\n const connection = await pool.getConnection();\n // Set when the ROLLBACK failed (`returnTransactionConnection`).\n let rollbackFailed = false;\n // What `beginTransaction` changed on the session, to put back once the\n // transaction has ended. Filled in as each change lands, so a setup that\n // fails part way still puts back what it had already changed.\n const restore: NonNullable<SessionRestore> = {};\n const settled = await this.settleOutermost(\n {\n context: this.createTransactionContext(connection),\n join: this.joinSteps(connection),\n openedAt,\n begin: () => this.beginTransaction(connection, options, restore),\n commit: async () => {\n await connection.query(\"COMMIT\");\n },\n // A failure is recorded for the return and rethrown, so the base\n // knows the ROLLBACK is unconfirmed.\n rollback: async () => {\n await connection.query(\"ROLLBACK\").catch((rollbackError: unknown) => {\n rollbackFailed = true;\n throw rollbackError;\n });\n },\n },\n work,\n options\n );\n // Returned before anything held runs, so an effect that opens a\n // transaction takes one connection, not two.\n await returnTransactionConnection(connection, rollbackFailed, restore);\n return settled;\n }\n\n /**\n * How a call made inside a transaction on `connection` joins it: flat, on\n * the same connection, with nothing to send. Any failure inside it fails\n * the whole transaction, since no savepoint can undo the part alone.\n */\n private joinSteps(connection: PoolConnection) {\n const callbackConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n return this.joinStepsOn(\n () => this.createTransactionContext(connection),\n relations =>\n relations\n ? drizzle({ client: callbackConnection, relations })\n : drizzle({ client: callbackConnection }),\n context => ({\n context,\n begin() {},\n commit() {},\n rollback() {},\n dooms: () => true,\n })\n );\n }\n\n /**\n * Get MySQL database capabilities.\n *\n * @remarks\n * MySQL has some limitations:\n * - No JSONB (uses JSON)\n * - No arrays\n * - No native ILIKE\n * - No RETURNING clause\n * - Savepoints disabled for safety\n */\n getCapabilities(): DatabaseCapabilities {\n return {\n dialect: \"mysql\",\n supportsJsonb: false, // MySQL uses JSON, not JSONB\n supportsJson: true,\n supportsArrays: false, // MySQL doesn't support array types\n supportsGeneratedColumns: true, // MySQL 5.7.6+\n supportsFts: true, // MySQL has FULLTEXT indexes\n supportsIlike: false, // No native ILIKE, use LOWER() LIKE\n supportsReturning: false, // No RETURNING clause in MySQL\n supportsSavepoints: false, // Disabled for safety per approved approach\n supportsOnConflict: true, // ON DUPLICATE KEY UPDATE\n maxParamsPerQuery: 65535, // MySQL limit\n maxIdentifierLength: 64, // MySQL limit\n sharedRowLock: this.sharedRowLock, // Known once connected\n functionalIndexes: this.functionalIndexes, // Known once connected\n };\n }\n\n /**\n * Build a placeholder for MySQL (uses ? instead of $1, $2, etc.)\n *\n * @param _index - Parameter index (ignored for MySQL)\n * @returns The ? placeholder\n */\n protected buildPlaceholder(_index: number): string {\n return \"?\";\n }\n\n /**\n * Build multiple placeholders for MySQL.\n *\n * @param count - Number of placeholders needed\n * @param _startIndex - Starting index (ignored for MySQL)\n * @returns Comma-separated ? placeholders\n */\n protected buildPlaceholders(count: number, _startIndex: number = 0): string {\n return Array(count).fill(\"?\").join(\", \");\n }\n\n /**\n * Escape an identifier for MySQL (uses backticks instead of double quotes).\n *\n * @param identifier - The identifier to escape\n * @returns Escaped identifier with backticks\n */\n protected escapeIdentifier(identifier: string): string {\n // MySQL uses backticks for identifiers\n return `\\`${identifier.replace(/`/g, \"``\")}\\``;\n }\n\n // ============================================================\n // Protected Helper Methods\n // ============================================================\n\n /**\n * Ensures pool is connected and returns it.\n *\n * @throws {DatabaseError} If not connected\n */\n private ensurePool(): Pool {\n if (!this.pool) {\n throw createDatabaseError({\n kind: \"connection\",\n message: \"MySqlAdapter is not connected. Call connect() first.\",\n });\n }\n return this.pool;\n }\n\n /**\n * Return the typed Drizzle instance for MySQL.\n * Guarded for server-only usage and requires an active connection.\n *\n * @param schema - Optional schema for relational queries (db.query.*)\n * @returns Drizzle ORM instance wrapping the mysql2 pool connection\n * @throws {Error} If called in browser or not connected\n */\n getDrizzle<T = MySql2Database<AnyRelations>>(relations?: AnyRelations): T {\n if (typeof window !== \"undefined\") {\n throw new Error(\"getDrizzle() is server-only\");\n }\n const pool = this.ensurePool();\n if (!relations) {\n this.drizzleBare ??= this.poolDrizzle(pool);\n return this.drizzleBare as T;\n }\n let cached = this.drizzleByRelations.get(relations);\n if (!cached) {\n cached = this.poolDrizzle(pool, relations);\n this.drizzleByRelations.set(relations, cached);\n }\n return cached as T;\n }\n\n /**\n * A Drizzle handle on the pool, for `getDrizzle`.\n *\n * Its `transaction` runs on a connection this adapter checks out and\n * returns itself (`pinDrizzleTransactions`): destroyed rather than pooled\n * when no ROLLBACK could be confirmed on it. And it counts for\n * `transactionOpenInFlow`, as the adapter's does.\n */\n private poolDrizzle(pool: Pool, relations?: AnyRelations) {\n // drizzle v1's mysql2 driver takes the CALLBACK pool and connection,\n // which the mysql2/promise wrappers expose on `.pool` and `.connection`:\n // handed a wrapper, it throws (\"Cannot set properties of undefined\n // (setting 'supportBigNumbers')\"). The pre-v1 `mode` option is gone.\n const onClient = (client: CallbackPool | CallbackConnection) =>\n relations ? drizzle({ client, relations }) : drizzle({ client });\n // The handle's own queries go to the pool, so it is handed a view of the\n // pool that reports them (`observedPool`).\n const callbackPool = this.observedPool(\n (pool as unknown as { pool: CallbackPool }).pool,\n [\"query\", \"execute\", \"getConnection\"]\n );\n // Its transactions join the one the calling flow is inside, as the\n // adapter's `transaction()` does, and open one of this adapter's\n // otherwise (`joiningHandle`).\n return this.joiningHandle(\n onClient(callbackPool),\n async () => {\n const connection = await pool.getConnection();\n return {\n drizzle: onClient(\n (connection as unknown as { connection: CallbackConnection })\n .connection\n ),\n rollback: () => connection.query(\"ROLLBACK\"),\n release: dirty => {\n if (dirty) connection.destroy();\n else connection.release();\n },\n join: this.joinSteps(connection),\n };\n },\n relations\n );\n }\n\n /**\n * Warns when a caller's configured driver zone does not agree with the\n * pinned UTC session.\n *\n * mysql2's driver-level `timezone` decides how it parses the date strings\n * the pinned session answers with and how it binds raw Date parameters, so\n * a non-UTC spelling there would shift values a round-trip fails to\n * cancel. The configuration is aligned to the pin (`buildConnectionConfig`\n * sets UTC unconditionally, for pooled and standalone connections alike),\n * never rewritten silently: the caller configured a reading, and this is\n * where that reading is named as lost. Runs from `connect`, once per pool\n * build — the configuration cannot change between calls.\n */\n private warnOnDriverZoneDisagreement(): void {\n // mysql2's own default is `local`, so an unset configuration is judged\n // the same way: it agrees only on a UTC host.\n const requestedZone = this.config.timezone ?? \"local\";\n if (isUtcDriverZone(requestedZone)) return;\n this.config.logger?.warn?.(\n `Driver timezone \"${requestedZone}\" is not UTC; it is aligned to the pinned UTC session and no longer shifts reads or writes.`\n );\n }\n\n /**\n * Builds mysql2 Pool configuration from adapter config.\n */\n private buildPoolConfig(): PoolOptions {\n const config: PoolOptions = this.buildConnectionConfig();\n\n // Pool settings - mysql2 uses different property names\n config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;\n config.idleTimeout =\n this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;\n\n // A request waits for a connection when the pool is full. The queue has\n // no length limit; each wait in it is bounded instead (`boundPoolWait`).\n config.waitForConnections = true;\n config.queueLimit = 0;\n\n return config;\n }\n\n /**\n * Builds the settings every connection opens with, pooled or not, from\n * adapter config.\n */\n private buildConnectionConfig(): ConnectionOptions {\n const config: ConnectionOptions = this.buildAddressConfig();\n\n config.connectTimeout =\n this.config.pool?.connectionTimeoutMs ??\n DEFAULT_POOL_CONFIG.connectionTimeoutMs;\n\n // SSL configuration\n if (this.config.ssl) {\n config.ssl = this.buildSslConfig(this.config.ssl);\n }\n\n // MySQL-specific options. Every connection this adapter opens serves a\n // UTC session (see the pins in `connect` and `runSession`), so the\n // driver zone is pinned with them: mysql2 parses the date strings a\n // pinned session answers with and binds raw Date parameters, and a\n // driver in one zone beside a session in another shifts values every\n // time a round-trip fails to cancel. A configured zone that is not UTC\n // is aligned rather than refused — the pin is the deliberate boundary —\n // and `connect` warns the caller, once, that the shift they configured\n // is gone.\n config.timezone = \"Z\";\n\n if (this.config.charset) {\n config.charset = this.config.charset;\n }\n\n // Enable multiple statements if needed (disabled by default for security)\n config.multipleStatements = false;\n\n // Date handling\n config.dateStrings = false; // Return Date objects\n\n return config;\n }\n\n /** Where to connect: the connection string, or the explicit options. */\n private buildAddressConfig(): ConnectionOptions {\n if (this.config.url) return { uri: this.config.url };\n const { host, port, database, user, password } = this.config;\n const config: ConnectionOptions = { host, port, database, user, password };\n // An option left unset is left out, for mysql2's own default.\n for (const key of Object.keys(config) as (keyof ConnectionOptions)[]) {\n if (!config[key]) delete config[key];\n }\n return config;\n }\n\n /** mysql2's `ssl` option for the adapter's `ssl` setting. */\n private buildSslConfig(\n ssl: NonNullable<MySqlAdapterConfig[\"ssl\"]>\n ): ConnectionOptions[\"ssl\"] {\n if (typeof ssl === \"boolean\") {\n return ssl ? {} : undefined;\n }\n return {\n rejectUnauthorized: ssl.rejectUnauthorized,\n ca: ssl.ca,\n cert: ssl.cert,\n key: ssl.key,\n };\n }\n\n /**\n * Begins a transaction with the specified options, recording in `changed`\n * each session setting it changes, as it changes it, for `restoreSession`\n * to put back.\n */\n private async beginTransaction(\n connection: PoolConnection,\n options: TransactionOptions | undefined,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n // Set isolation level if specified (must be done before BEGIN)\n if (options?.isolationLevel) {\n const isolationMap: Record<string, string> = {\n \"read uncommitted\": \"READ UNCOMMITTED\",\n \"read committed\": \"READ COMMITTED\",\n \"repeatable read\": \"REPEATABLE READ\",\n serializable: \"SERIALIZABLE\",\n };\n const level = isolationMap[options.isolationLevel];\n if (level) {\n await connection.query(`SET TRANSACTION ISOLATION LEVEL ${level}`);\n }\n }\n\n // Set read-only mode if specified\n if (options?.readOnly) {\n await connection.query(\"SET TRANSACTION READ ONLY\");\n }\n\n // Begin the transaction\n await connection.query(\"START TRANSACTION\");\n\n // Set lock wait timeout if specified\n if (options?.timeoutMs) {\n // Read first, so the session's own value can be put back once the\n // transaction has ended (`SessionRestore`).\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout\"\n );\n const previous = Number(rows[0]?.timeout);\n // MySQL uses seconds for lock_wait_timeout\n const timeoutSeconds = Math.ceil(options.timeoutMs / 1000);\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`\n );\n changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;\n }\n\n if (options?.strictSqlMode) {\n await this.enterStrictMode(connection, changed);\n }\n }\n\n /**\n * Strict mode for the rest of this connection's session: an INSERT, UPDATE\n * or ALTER that would cut a string to its column's length, or clamp a\n * number to its range, fails instead of applying with a warning. Added to\n * the session's own mode, so every other flag it had still holds, and\n * recorded in `changed` for `restoreSession` to put back.\n */\n private async enterStrictMode(\n connection: Queryable,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.sql_mode AS mode\"\n );\n const previous: unknown = rows[0]?.mode;\n // Recorded before the SET: one the server applied but whose reply was\n // lost still has its mode put back.\n changed.sqlMode = typeof previous === \"string\" ? previous : null;\n await connection.query(\n \"SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))\"\n );\n }\n\n /**\n * Creates a TransactionContext for the given connection.\n *\n * @remarks\n * Note: Savepoint methods are not implemented (set to undefined)\n * as savepoints are disabled in this adapter per approved approach.\n */\n /**\n * Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL\n * and SQLite, on the tables where MySQL can keep that promise.\n *\n * MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`\n * fires on a collision with ANY unique key, and a failed insert does not\n * say reliably which one. So the conflict target must be the table's only\n * unique key apart from its primary key, and any other table is refused\n * before anything is written. On such a table the row is inserted first —\n * its validation and triggers apply, as they do before `ON CONFLICT`\n * elsewhere — and a duplicate is resolved by a locking read of the target\n * key, which sees the latest committed row rather than the transaction's\n * snapshot. The row found is kept as it is when there is nothing to update\n * (a no-op assignment would still run its update triggers), or updated by\n * its key. No row there means the collision was on the primary key, and it\n * is refused as `ON CONFLICT (target)` refuses it.\n *\n * The steps run in the caller's transaction, or in one of their own, since a\n * locking read locks nothing past its statement under autocommit. Rows are\n * read back by their primary key, so a trigger that rewrites the conflict\n * value still returns the row stored. The conflict columns must also be\n * given values: MySQL cannot look a row up by a key the database would only\n * fill in itself. A conflict is resolved on the caller's key value: a\n * BEFORE INSERT trigger that moves the key onto another row's value is\n * refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a\n * caller's transaction stays usable.\n */\n override async upsert<T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions,\n executor?: unknown\n ): Promise<T> {\n const tableObj = this.getTableObject(table) as MySqlTable | undefined;\n // Unknown table: the base method reports it as every CRUD method does.\n if (!tableObj) return super.upsert<T>(table, data, options, executor);\n let target: { where: SQL | undefined; nullKey: boolean };\n try {\n // Judged before anything else, so a refused call takes no connection.\n target = this.upsertTarget(tableObj, table, data, options);\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n // A locking read holds its lock only inside a transaction: under autocommit\n // it is released when its own statement ends, and the next statement may\n // run on another pooled connection. Without the caller's transaction, the\n // insert, the read and the update get one of their own, on one connection.\n if (executor === undefined) {\n return this.transaction(tx => tx.upsert<T>(table, data, options));\n }\n try {\n const db = executor as MySql2Database<AnyRelations>;\n // A conflict key holding NULL never conflicts: a unique index admits any\n // number of NULLs. It is a plain insert, read back by its primary key.\n if (target.nullKey) {\n return ((await this.insertRow(db, tableObj, data)) ?? data) as T;\n }\n return (await this.insertOrResolve(\n db,\n tableObj,\n data,\n options,\n target.where\n )) as T;\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n }\n\n /**\n * What an upsert is keyed on: the target's condition, and whether its key\n * holds a NULL. Refuses a target this dialect cannot keep to, and a conflict\n * column left for the database to fill — MySQL cannot look a row up by a key\n * value only the database would supply.\n */\n private upsertTarget(\n tableObj: MySqlTable,\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions\n ): { where: SQL | undefined; nullKey: boolean } {\n // MySQL needs every conflict value whether or not it updates: the row is\n // found by them after a duplicate, so the keep-existing rule applies to\n // every call here.\n this.assertUpsertTarget(table, data, options, true);\n const key = this.conflictKey(tableObj, table, options.conflictColumns);\n const values = options.conflictColumns.map(column => data[column]);\n return {\n where: and(...key.map((column, i) => eq(column, values[i] as never))),\n nullKey: values.some(value => value === null),\n };\n }\n\n /**\n * The table's columns for a conflict target, refusing a target MySQL cannot\n * keep to: one that is not a primary or unique key, which an update could\n * spread over several rows, and one beside which the table has another\n * unique key, a collision on which MySQL would not tell apart.\n */\n private conflictKey(\n tableObj: MySqlTable,\n table: string,\n conflictColumns: readonly string[]\n ): Column[] {\n const record = tableObj as unknown as Record<string, Column | undefined>;\n const columns = conflictColumns.map(name => record[name]);\n const names = new Set(columns.map(column => column?.name));\n const isTarget = (key: readonly string[]) =>\n key.length === names.size && key.every(name => names.has(name));\n const { primary, unique } = keysOf(tableObj);\n // Every row this upsert returns is read back by its primary key, so a\n // table without one has no stable way to find the row it wrote.\n if (primary.length === 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`,\n });\n }\n const known = columns.every(column => column !== undefined);\n if (\n !known ||\n !(isTarget(primary) || unique.some(k => isTarget(k.columns)))\n ) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: the conflict columns (${conflictColumns.join(\", \")}) are not a primary or unique key of the table.`,\n });\n }\n const others = unique.filter(key => !isTarget(key.columns));\n if (others.length > 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL cannot limit a duplicate to the conflict columns (${conflictColumns.join(\", \")}) while the table has other unique keys (${others.map(key => key.name).join(\", \")}). Upsert only on a table whose one unique key besides the primary key is the conflict target, or insert and update inside a transaction.`,\n });\n }\n return columns;\n }\n\n /** The update an upsert applies to an existing row: never its key. */\n private updateValues(\n data: Record<string, unknown>,\n options: UpsertOptions\n ): Record<string, unknown> {\n const key = new Set(options.conflictColumns);\n const update: Record<string, unknown> = {};\n for (const column of options.updateColumns ?? Object.keys(data)) {\n if (!key.has(column) && column in data) update[column] = data[column];\n }\n return update;\n }\n\n /** The row matching `where`, by a locking (current) read. */\n private async lockedRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n where: SQL | undefined\n ): Promise<unknown> {\n const rows = await db\n .select()\n .from(tableObj)\n .where(where)\n .limit(1)\n .for(\"update\");\n return rows[0];\n }\n\n /**\n * Insert the row, or resolve a duplicate on the target key. The insert is\n * read back by the primary key it reports, not by the conflict key: a\n * BEFORE INSERT trigger may have rewritten the conflict value, and the row\n * stored is the one to return.\n *\n * A duplicate is resolved when a row holds the TARGET key, whichever unique\n * key MySQL names in its error. PostgreSQL checks the target's index before\n * any other, so a row colliding on the target and on another key resolves\n * on the target there too; refusing it here because MySQL happened to check\n * the other index first would answer that case differently. A target row\n * committed by another transaction after this insert failed is the order in\n * which that transaction ran first, where PostgreSQL also resolves on it.\n */\n private async insertOrResolve(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>,\n options: UpsertOptions,\n where: SQL | undefined\n ): Promise<unknown> {\n let failure: unknown;\n try {\n const stored = await this.insertRow(db, tableObj, data);\n return stored ?? (await this.lockedRow(db, tableObj, where));\n } catch (error) {\n // Classified as everything else is, through the driver's own error\n // under Drizzle's wrapper (`classifyError`).\n if (this.classifyError(error).kind !== \"unique_violation\") throw error;\n failure = error;\n }\n const existing = await this.lockedRow(db, tableObj, where);\n // No row holds the target key: the collision was on another unique key,\n // refused as `ON CONFLICT (target)` refuses it, as the unique violation the\n // classifier reads it to be.\n if (existing === undefined) throw failure;\n const update = this.updateValues(data, options);\n if (Object.keys(update).length === 0) return existing;\n await db.update(tableObj).set(update).where(where);\n // Read back by the row's primary key as the update left it: the update may\n // set the key itself (every given column is updated by default, as `ON\n // CONFLICT DO UPDATE` does elsewhere), and a BEFORE UPDATE trigger may\n // have changed the conflict value the update was keyed on. A trigger that\n // changed the primary key instead leaves the conflict value standing, so\n // the row is looked for there next; one that changed both is not found.\n const identity = this.byIdentity(tableObj, {\n ...(existing as Record<string, unknown>),\n ...update,\n });\n const updated =\n identity === undefined\n ? undefined\n : await this.lockedRow(db, tableObj, identity);\n return updated ?? this.lockedRow(db, tableObj, where);\n }\n\n /**\n * Insert a row and read it back by its primary key: the value the caller\n * gave, or the one MySQL or the model generated, which `$returningId()`\n * reports and a given value it does not. Undefined when the table has no\n * primary key to read by.\n */\n private async insertRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>\n ): Promise<unknown> {\n const [generated] = (await db\n .insert(tableObj)\n .values(data as never)\n .$returningId()) as Record<string, unknown>[];\n // A key the caller gave wins; one given as undefined or null does not hide\n // the value MySQL generated for it.\n const given = Object.fromEntries(\n Object.entries(data).filter(\n ([, value]) => value !== undefined && value !== null\n )\n );\n const identity = this.byIdentity(tableObj, { ...generated, ...given });\n return identity === undefined\n ? undefined\n : this.lockedRow(db, tableObj, identity);\n }\n\n /**\n * A row's primary key as a condition, from a record keyed as the model's\n * properties are. Undefined when the table has no primary key or the record\n * lacks part of it.\n */\n private byIdentity(\n tableObj: MySqlTable,\n record: Record<string, unknown>\n ): SQL | undefined {\n const primary = new Set(keysOf(tableObj).primary);\n const parts = Object.entries(getColumns(tableObj)).filter(([, column]) =>\n primary.has(column.name)\n );\n if (\n parts.length === 0 ||\n parts.some(\n ([name]) => record[name] === undefined || record[name] === null\n )\n ) {\n return undefined;\n }\n return and(\n ...parts.map(([name, column]) => eq(column, record[name] as never))\n );\n }\n\n /** A table-level `primaryKey({ columns })`, read through the MySQL table config. */\n protected override compositePrimaryKey(\n tableObj: Record<string, unknown>\n ): object[] {\n return getTableConfig(\n tableObj as unknown as MySqlTable\n ).primaryKeys.flatMap(key => key.columns);\n }\n\n private createTransactionContext(\n connection: PoolConnection\n ): TransactionContext {\n // Bind a Drizzle instance to this transaction's checked-out connection so\n // the delegated CRUD methods run inside the transaction and see its\n // uncommitted rows. drizzle's mysql2 driver needs the underlying CALLBACK\n // connection, which the mysql2/promise wrapper exposes on `.connection`\n // (mirrors the `.pool` unwrap in getDrizzle()); getDrizzle() itself wraps\n // the pool, which would use a different connection. Built lazily and\n // memoized: transactions that use only raw execute/insert never construct\n // it.\n const txConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n const txHandles = this.transactionDrizzleHandles(relations =>\n relations\n ? drizzle({ client: txConnection, relations })\n : drizzle({ client: txConnection })\n );\n const txDb = txHandles.bare;\n return {\n drizzleWithRelations: txHandles.withRelations,\n\n execute: async <T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> => {\n const [rows] = await connection.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n return rows as T[];\n },\n\n // Run on the transaction-bound Drizzle instance rather than the pool, so\n // the statement is part of this transaction and sees its uncommitted rows.\n runStatement: async (statement: SQL): Promise<void> => {\n await txDb().execute(statement);\n },\n\n // mysql2 answers a `[rows, fields]` tuple; the transaction-bound instance\n // keeps the read inside this transaction so it sees its uncommitted writes.\n queryStatement: async <T = Record<string, unknown>>(\n statement: SQL\n ): Promise<T[]> => {\n const result = await txDb().execute(statement);\n // A tuple's first element is the rows. Anything else was not understood,\n // and must not reach a caller as \"there is nothing there\" — the same\n // reason the pooled `queryStatement` refuses rather than answering\n // empty.\n if (!Array.isArray(result)) {\n throw this.createDatabaseError(\n \"query\",\n \"Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.\",\n undefined\n );\n }\n return result[0] as unknown as T[];\n },\n\n lockRow: async (table: string, id: SqlParam): Promise<void> => {\n const idColumn = this.escapeIdentifier(\"id\");\n await connection.query(\n `SELECT ${idColumn} FROM ${this.escapeIdentifier(table)} ` +\n `WHERE ${idColumn} = ? FOR UPDATE`,\n [id] as unknown[]\n );\n },\n\n insert: async <T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options?: InsertOptions\n ): Promise<T> => {\n // Refused before any statement, as the pooled insert is: a table this\n // instance never registered may not match what its code would write.\n const insertTableObj = this.resolvedTableObject(table);\n const mapped = this.mapRowToRawSql(insertTableObj, data);\n const columns = Object.keys(mapped);\n const values = Object.values(mapped);\n const placeholders = this.buildPlaceholders(values.length, 0);\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES (${placeholders})`;\n\n const [result] = await connection.query<ResultSetHeader>(sql, values);\n\n const ret = options?.returning;\n // No columns requested: skip the select-back reread entirely.\n if (Array.isArray(ret) && ret.length === 0) {\n return undefined as T;\n }\n\n // MySQL has no RETURNING; select the inserted row back. Project only the\n // requested columns so a large JSON snapshot is not read unless asked.\n // Each timestamp's wall clock, spelled out by the database. mysql2\n // turns one into a `Date` in the LOCAL zone before this code sees it,\n // and that conversion cannot be undone: a wall clock inside a\n // daylight-saving gap is normalized away.\n const aliases = this.dateWallClockAliases(insertTableObj, ret ?? \"*\");\n const spelled = aliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const projected =\n !ret || ret === \"*\"\n ? \"*\"\n : this.mapColumnNamesToSql(insertTableObj, ret)\n .map(c => this.escapeIdentifier(c))\n .join(\", \");\n const selectList = spelled ? `${projected}, ${spelled}` : projected;\n\n // Prefer the primary key: auto-increment via insertId, otherwise a\n // supplied id (manually-keyed tables like nextly_versions). Matching by\n // all values is a last resort because `col = NULL` never matches, so a\n // row with nullable columns would not be found by that path.\n const idValue = result.insertId ? result.insertId : mapped.id;\n if (idValue !== undefined) {\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE id = ?`,\n [idValue]\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n }\n\n const whereClauses = columns.map(\n c => `${this.escapeIdentifier(c)} = ?`\n );\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE ${whereClauses.join(\" AND \")} LIMIT 1`,\n values\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n },\n\n insertMany: async <T = unknown>(\n table: string,\n data: Record<string, unknown>[],\n options?: InsertOptions\n ): Promise<T[]> => {\n if (data.length === 0) return [];\n const retMany = options?.returning;\n const skipReread = Array.isArray(retMany) && retMany.length === 0;\n\n // Refused before any statement, as `insert` above is.\n const tableObj = this.resolvedTableObject(table);\n const mappedRecords = data.map(r => this.mapRowToRawSql(tableObj, r));\n const columns = Object.keys(mappedRecords[0]);\n const allValues: unknown[] = [];\n const valuesClauses: string[] = [];\n\n for (const record of mappedRecords) {\n const placeholders: string[] = [];\n for (const col of columns) {\n allValues.push(record[col]);\n placeholders.push(\"?\");\n }\n valuesClauses.push(`(${placeholders.join(\", \")})`);\n }\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES ${valuesClauses.join(\", \")}`;\n\n const [result] = await connection.query<ResultSetHeader>(\n sql,\n allValues\n );\n\n // For bulk insert, we need to SELECT the inserted rows\n // MySQL's insertId gives the first auto-increment ID\n if (!skipReread && result.insertId && result.affectedRows > 0) {\n const ids: number[] = [];\n for (let i = 0; i < result.affectedRows; i++) {\n ids.push(result.insertId + i);\n }\n const placeholders = ids.map(() => \"?\").join(\", \");\n const bulkAliases = this.dateWallClockAliases(tableObj, \"*\");\n const bulkSpelled = bulkAliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT *${bulkSpelled ? `, ${bulkSpelled}` : \"\"} FROM ${this.escapeIdentifier(table)} WHERE id IN (${placeholders})`,\n ids\n );\n return (rows as T[]).map(r =>\n this.mapRowFromRawSql(tableObj, r, bulkAliases)\n );\n }\n\n // Fallback: return empty if we can't determine inserted rows\n return [];\n },\n\n // Adapter-built, as `insert` above is; `transactionUpdate` says why.\n // A column the model does not declare binds natively, as every value\n // on this path's insert does.\n update: this.transactionUpdate(\n txDb,\n async statement => {\n // No RETURNING on MySQL: nothing to report, the read-back is by\n // the caller's predicate.\n await txDb().execute(statement);\n return undefined;\n },\n // Structured values as JSON text: mysql2's formatter spells an object\n // as `key = value` pairs and an array as a value list, neither JSON.\n value => this.bindUnmodeledStructuredAsJson(value)\n ),\n\n ...this.createTransactionForwarders(txDb),\n\n // Savepoints disabled per approved approach\n savepoint: undefined,\n rollbackToSavepoint: undefined,\n releaseSavepoint: undefined,\n };\n }\n\n /**\n * Classifies a MySQL error into a DatabaseError.\n *\n * @param error - Original error from mysql2\n * @param sql - SQL statement that caused the error (optional)\n * @returns DatabaseError with proper classification\n */\n protected override classifyError(\n error: unknown,\n sql?: string\n ): DatabaseError {\n // Why short-circuit on existing DatabaseError: F17's\n // UnsupportedDialectVersionError is already a typed DatabaseError with\n // kind: \"unsupported_version\" plus detectedVersion/requiredVersion\n // fields. Re-wrapping it here would erase those fields and re-tag it\n // as kind: \"unknown\".\n if (isDatabaseError(error)) return error;\n\n // Classified from mysql2's own error, which Drizzle puts on `cause` when it\n // wraps a failed query (`driverErrorIn`); it is the one carrying `errno`.\n const mysqlError = this.driverErrorIn<{\n errno?: number;\n code?: string;\n sqlState?: string;\n message?: string;\n sql?: string;\n cause?: unknown;\n }>(error, candidate => typeof candidate.errno === \"number\");\n\n // Determine error kind from MySQL error number\n const kind: DatabaseErrorKind =\n (mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno]) || \"unknown\";\n\n // Build error message\n let message = mysqlError.message ?? String(error);\n if (sql && kind === \"query\") {\n message = `Query failed: ${message}`;\n }\n\n return createDatabaseError({\n kind,\n message,\n code: mysqlError.code ?? mysqlError.errno?.toString(),\n detail: mysqlError.sql,\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\n/** A table's keys by database column name, as its model declares them. */\ninterface TableKeys {\n primary: string[];\n unique: { name: string; columns: string[] }[];\n}\n\nconst TABLE_KEYS = new WeakMap<MySqlTable, TableKeys>();\n\n/**\n * A table's primary key and its unique keys other than the primary key, read\n * from its model once and kept for the table's lifetime: a model is not\n * changed in place, a new schema brings new table objects.\n */\nfunction keysOf(tableObj: MySqlTable): TableKeys {\n const cached = TABLE_KEYS.get(tableObj);\n if (cached) return cached;\n const config = getTableConfig(tableObj);\n const primary = [\n ...config.columns.filter(column => column.primary),\n ...config.primaryKeys.flatMap(key => key.columns),\n ].map(column => column.name);\n const unique = [\n ...config.columns\n .filter(column => column.isUnique && !column.primary)\n .map(column => ({\n // Drizzle's own name for an unnamed column-level unique key.\n name: column.uniqueName ?? `${config.name}_${column.name}_unique`,\n columns: [column.name],\n })),\n ...config.uniqueConstraints.map(constraint => ({\n name: constraint.name,\n columns: constraint.columns.map(column => column.name),\n })),\n ...config.indexes\n .filter(index => index.config.unique === true)\n .map(index => ({\n name: index.config.name,\n columns: (index.config.columns as { name: string }[]).map(\n column => column.name\n ),\n })),\n ];\n const keys = { primary, unique };\n TABLE_KEYS.set(tableObj, keys);\n return keys;\n}\n\n/**\n * Create a MySQL database adapter.\n *\n * @param config - MySQL adapter configuration\n * @returns A new MySqlAdapter instance\n *\n * @example\n * ```typescript\n * // Simple usage with URL\n * const adapter = createMySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * });\n *\n * // Full configuration\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * pool: {\n * min: 2,\n * max: 20,\n * idleTimeoutMs: 30000,\n * connectionTimeoutMs: 10000,\n * },\n * ssl: {\n * rejectUnauthorized: true,\n * },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport function createMySqlAdapter(config: MySqlAdapterConfig): MySqlAdapter {\n return new MySqlAdapter(config);\n}\n\n/**\n * Type guard to check if a value is a MySqlAdapter.\n *\n * @param value - Value to check\n * @returns True if value is a MySqlAdapter instance\n *\n * @example\n * ```typescript\n * if (isMySqlAdapter(adapter)) {\n * // TypeScript knows adapter is MySqlAdapter\n * console.log('Using MySQL');\n * }\n * ```\n */\nexport function isMySqlAdapter(value: unknown): value is MySqlAdapter {\n return value instanceof MySqlAdapter;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/pool-wait.ts","../src/index.ts"],"sourcesContent":["/**\n * The bounded wait for a pooled connection.\n *\n * mysql2 has no such bound. A request that finds every connection in use is\n * queued until one is released, however long that takes, so a database that\n * stays busy holds every request behind it indefinitely. This bounds the wait\n * the way pg-pool's `connectionTimeoutMillis` does: past the bound, the\n * request fails with the adapters' own pool-wait timeout error, which core\n * answers with \"database busy\".\n *\n * Only the wait in the queue: a request that opens a new connection is bounded\n * by mysql2's `connectTimeout` instead, set from the same setting, and a\n * connection that cannot be opened is a database that cannot be reached\n * rather than one that is busy.\n */\nimport { poolWaitTimeoutError } from \"@nextlyhq/adapter-drizzle/types\";\n\n/** A connection the pool hands out, as far as the bound needs one. */\ninterface QueuedConnection {\n release(): void;\n}\n\ntype GetConnectionCallback = (\n err: Error | null,\n connection?: QueuedConnection\n) => void;\n\n/**\n * The part of mysql2's callback pool the bound uses. Declared here because\n * mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\nexport interface QueueingPool {\n getConnection(callback: GetConnectionCallback): void;\n once(event: \"enqueue\", listener: () => void): unknown;\n removeListener(event: \"enqueue\", listener: () => void): unknown;\n /** mysql2's queue of waiting requests (a Denque). */\n _connectionQueue?: {\n length: number;\n peekAt(index: number): unknown;\n removeOne(index: number): unknown;\n };\n}\n\n/**\n * Takes a request that gave up out of the pool's queue, so a pool that stays\n * saturated does not collect every request that has already failed, and a\n * released connection goes to one still waiting. Returns whether it was\n * there: with diagnostics tracing on, mysql2 queues a wrapper of the\n * callback rather than the callback itself, and then it stays queued until a\n * connection reaches it.\n */\nfunction leaveQueue(pool: QueueingPool, waiting: GetConnectionCallback) {\n const queue = pool._connectionQueue;\n if (!queue) return false;\n for (let at = 0; at < queue.length; at++) {\n if (queue.peekAt(at) === waiting) {\n queue.removeOne(at);\n return true;\n }\n }\n return false;\n}\n\n/**\n * Makes `pool` fail a request that has waited `boundMs` in its queue for a\n * connection, and returns it. A bound of 0 leaves the wait unbounded, as\n * pg-pool reads a `connectionTimeoutMillis` of 0.\n *\n * Replaces the pool's own `getConnection`, which its `query` and `execute`\n * go through, so every path to a connection is bounded: the adapter's\n * statements, its transactions, and the Drizzle handle core services query\n * through.\n *\n * A request that gives up leaves the queue. Where mysql2 cannot be asked to\n * drop it, the connection it is eventually handed is released straight back\n * rather than held by a request that has already failed.\n */\nexport function boundPoolWait<P extends QueueingPool>(\n pool: P,\n boundMs: number\n): P {\n if (boundMs <= 0) return pool;\n const getConnection = pool.getConnection.bind(pool);\n const bounded = (callback: GetConnectionCallback) => {\n let answered = false;\n // Set only once the request has queued; read by the answer that ends it.\n const wait: { timer?: ReturnType<typeof setTimeout> } = {};\n // mysql2 emits `enqueue` synchronously, inside this call, exactly when\n // the request joins the queue rather than taking or opening a connection.\n let queued = false;\n const onEnqueue = () => (queued = true);\n pool.once(\"enqueue\", onEnqueue);\n const waiting: GetConnectionCallback = (err, connection) => {\n if (answered) {\n connection?.release();\n return;\n }\n answered = true;\n clearTimeout(wait.timer);\n callback(err, connection);\n };\n getConnection(waiting);\n pool.removeListener(\"enqueue\", onEnqueue);\n if (!queued || answered) return;\n wait.timer = setTimeout(() => {\n answered = true;\n leaveQueue(pool, waiting);\n callback(poolWaitTimeoutError(boundMs));\n }, boundMs);\n };\n pool.getConnection = bounded;\n return pool;\n}\n","/**\n * @nextlyhq/adapter-mysql\n *\n * MySQL database adapter for Nextly.\n * Extends DrizzleAdapter from @nextlyhq/adapter-drizzle to provide MySQL-specific functionality.\n *\n * @remarks\n * This adapter uses the mysql2 package for database connectivity and provides:\n * - Connection pooling via mysql2 Pool\n * - Full transaction support with isolation levels\n * - CRUD operations with workarounds for missing RETURNING clause\n * - MySQL-specific error classification\n * - Automatic retry for deadlocks (error 1213)\n *\n * @example\n * ```typescript\n * import { createMySqlAdapter } from '@nextlyhq/adapter-mysql';\n *\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * });\n *\n * await adapter.connect();\n *\n * // Query data\n * const users = await adapter.select('users', {\n * where: { and: [{ column: 'status', op: '=', value: 'active' }] },\n * limit: 10,\n * });\n *\n * await adapter.disconnect();\n * ```\n *\n * @packageDocumentation\n */\n\nimport { DrizzleAdapter } from \"@nextlyhq/adapter-drizzle\";\n// F17: connect-time DB version check shared across all adapters.\nimport {\n createDatabaseError,\n isDatabaseError,\n type MySqlAdapterConfig,\n type ConnectionInspection,\n type DatabaseCapabilities,\n type PoolStats,\n type TransactionContext,\n type TransactionOptions,\n type SessionOptions,\n type StatementSession,\n type SqlParam,\n type WhereClause,\n type WhereCondition,\n type WhereOperator,\n type SelectOptions,\n type InsertOptions,\n type UpdateOptions,\n type DeleteOptions,\n type UpsertOptions,\n type OrderBySpec,\n type JoinSpec,\n type DatabaseError,\n type DatabaseErrorKind,\n type BaseAdapterConfig,\n type AdapterLogger,\n type PoolConfig,\n type SslConfig,\n} from \"@nextlyhq/adapter-drizzle/types\";\nimport {\n acceptsForShare,\n checkDialectVersion,\n} from \"@nextlyhq/adapter-drizzle/version-check\";\nimport { and, eq, getColumns } from \"drizzle-orm\";\nimport type { AnyRelations, Column, SQL } from \"drizzle-orm\";\nimport { getTableConfig, type MySqlTable } from \"drizzle-orm/mysql-core\";\nimport { drizzle, type MySql2Database } from \"drizzle-orm/mysql2\";\nimport type {\n Pool as CallbackPool,\n Connection as CallbackConnection,\n} from \"mysql2\";\nimport mysql from \"mysql2/promise\";\nimport type {\n ConnectionOptions,\n PoolOptions,\n RowDataPacket,\n ResultSetHeader,\n} from \"mysql2/promise\";\n\nimport { boundPoolWait, type QueueingPool } from \"./pool-wait\";\n\n// mysql2 type definitions use mixin patterns that TypeScript struggles with.\n// We define explicit interfaces for the methods we need.\n\n/**\n * Query result type - either rows or a result header\n */\ntype QueryResult = RowDataPacket[] | RowDataPacket[][] | ResultSetHeader;\n\n/**\n * Queryable interface for mysql2 connections\n */\ninterface Queryable {\n query<T extends QueryResult>(sql: string): Promise<[T, unknown]>;\n query<T extends QueryResult>(\n sql: string,\n values: unknown[]\n ): Promise<[T, unknown]>;\n}\n\n/**\n * mysql2 Pool interface with query method\n */\ninterface Pool extends Queryable {\n getConnection(): Promise<PoolConnection>;\n end(): Promise<void>;\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n}\n\n/**\n * A mysql2 connection of its own, outside the pool\n */\ninterface OwnConnection extends Queryable {\n /** Closes the connection. */\n destroy(): void;\n}\n\n/**\n * mysql2 PoolConnection interface with query and release methods\n */\ninterface PoolConnection extends Queryable {\n /** Returns the connection to the pool, for the next request to use. */\n release(): void;\n /** Closes the connection and removes it from the pool. */\n destroy(): void;\n}\n\n/**\n * What a transaction changed on its connection's session, to put back before\n * the connection returns to the pool; null when it changed nothing.\n *\n * MySQL has no transaction-scoped form of `innodb_lock_wait_timeout` or\n * `sql_mode`: a `SET SESSION` outlives the transaction, and the pooled\n * connection would carry one transaction's setting into every later query\n * handed it. Each field present is the session's value from before the\n * change, or null when it could not be read, and then the connection cannot\n * be put back.\n */\ntype SessionRestore = {\n lockWaitTimeout?: number | null;\n sqlMode?: string | null;\n} | null;\n\n/**\n * Puts back what a transaction changed on its connection's session. False\n * when that could not be done, and the connection must not be pooled.\n */\nasync function restoreSession(\n connection: PoolConnection,\n restore: SessionRestore\n): Promise<boolean> {\n if (restore === null) return true;\n if (restore.lockWaitTimeout === null || restore.sqlMode === null) {\n return false;\n }\n try {\n if (restore.lockWaitTimeout !== undefined) {\n // An integer read from the server, never caller input.\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`\n );\n }\n if (restore.sqlMode !== undefined) {\n // Read from the server, and bound as a value all the same.\n await connection.query(\"SET SESSION sql_mode = ?\", [restore.sqlMode]);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ends a transaction's hold on its connection: back to the pool once its\n * session is as the transaction found it, or closed when it is not. A\n * connection whose ROLLBACK failed (`rollbackFailed`) may still be inside\n * the transaction, and the next request handed it would write into a\n * transaction nobody commits.\n */\nasync function returnTransactionConnection(\n connection: PoolConnection,\n rollbackFailed: boolean,\n restore: SessionRestore\n): Promise<void> {\n if (!rollbackFailed && (await restoreSession(connection, restore))) {\n connection.release();\n } else {\n connection.destroy();\n }\n}\n\n/**\n * A newly opened pooled connection, as far as the time-zone pin needs one.\n *\n * The callback form of `query` is load-bearing twice: a pin that failed with\n * nothing handling the error would end up an unhandled `error` event on the\n * connection, and a failure swallowed without this `destroy` would leave an\n * unpinned session in the pool to serve shifted values.\n */\ninterface PinnableConnection {\n query(sql: string, callback: (error: Error | null) => void): void;\n destroy(): void;\n}\n\n/**\n * The part of mysql2's callback pool the time-zone pin uses. Declared here\n * because mysql2's mixin typings drop the EventEmitter methods its pool has.\n */\ninterface PinnablePool {\n on(\n event: \"connection\",\n listener: (connection: PinnableConnection) => void\n ): unknown;\n}\n\n/**\n * mysql2 accepts only `local`, `Z` and `±HH:MM` spellings for its driver\n * zone, and the ones that resolve to UTC are exactly these. `local` resolves\n * to the Node process's own zone, so it agrees with a UTC session only on a\n * UTC host.\n */\nfunction isUtcDriverZone(zone: string): boolean {\n if (zone === \"local\") return new Date().getTimezoneOffset() === 0;\n return zone === \"Z\" || zone === \"+00:00\" || zone === \"-00:00\";\n}\n\n// Re-export types for convenience\nexport type {\n MySqlAdapterConfig,\n DatabaseCapabilities,\n PoolStats,\n TransactionContext,\n TransactionOptions,\n SqlParam,\n WhereClause,\n WhereCondition,\n WhereOperator,\n SelectOptions,\n InsertOptions,\n UpdateOptions,\n DeleteOptions,\n UpsertOptions,\n OrderBySpec,\n JoinSpec,\n DatabaseError,\n DatabaseErrorKind,\n BaseAdapterConfig,\n AdapterLogger,\n PoolConfig,\n SslConfig,\n};\n\n/**\n * Package version\n */\nexport const VERSION = \"0.1.0\";\n\n/**\n * Default pool configuration values.\n */\nconst DEFAULT_POOL_CONFIG = {\n min: 2,\n max: 10,\n idleTimeoutMs: 30000,\n // How long a request waits for a connection, in the queue or opening one:\n // the same bound, and the same default, as the PostgreSQL adapter's.\n connectionTimeoutMs: 15000,\n};\n\n/**\n * MySQL error codes mapping to DatabaseErrorKind.\n *\n * @see https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html\n */\nconst MYSQL_ERROR_CODES: Record<number, DatabaseErrorKind> = {\n // Unique/Duplicate key violations\n 1022: \"unique_violation\", // ER_DUP_KEY\n 1062: \"unique_violation\", // ER_DUP_ENTRY\n 1169: \"unique_violation\", // ER_DUP_UNIQUE\n 1586: \"unique_violation\", // ER_DUP_ENTRY_WITH_KEY_NAME\n\n // Foreign key violations\n 1216: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW\n 1217: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED\n 1451: \"foreign_key_violation\", // ER_ROW_IS_REFERENCED_2\n 1452: \"foreign_key_violation\", // ER_NO_REFERENCED_ROW_2\n\n // Not null violations\n 1048: \"not_null_violation\", // ER_BAD_NULL_ERROR\n 1364: \"not_null_violation\", // ER_NO_DEFAULT_FOR_FIELD\n\n // Check constraint violations (MySQL 8.0.16+)\n 3819: \"check_violation\", // ER_CHECK_CONSTRAINT_VIOLATED\n\n // Deadlock\n 1213: \"deadlock\", // ER_LOCK_DEADLOCK\n\n // Timeout\n 1205: \"timeout\", // ER_LOCK_WAIT_TIMEOUT\n\n // Connection errors\n 1040: \"connection\", // ER_CON_COUNT_ERROR - Too many connections\n 1042: \"connection\", // ER_BAD_HOST_ERROR\n 1043: \"connection\", // ER_HANDSHAKE_ERROR\n 1044: \"connection\", // ER_DBACCESS_DENIED_ERROR\n 1045: \"connection\", // ER_ACCESS_DENIED_ERROR\n 1129: \"connection\", // ER_HOST_IS_BLOCKED\n 1130: \"connection\", // ER_HOST_NOT_PRIVILEGED\n 2002: \"connection\", // CR_CONNECTION_ERROR\n 2003: \"connection\", // CR_CONN_HOST_ERROR\n 2006: \"connection\", // CR_SERVER_GONE_ERROR\n 2013: \"connection\", // CR_SERVER_LOST\n\n // Query errors\n 1064: \"query\", // ER_PARSE_ERROR\n 1146: \"query\", // ER_NO_SUCH_TABLE\n 1054: \"query\", // ER_BAD_FIELD_ERROR\n};\n\n/** ER_LOCK_DEADLOCK: the transaction lost a race, and running it again may succeed. */\nconst DEADLOCK = 1213;\n\n/**\n * The MySQL error number an error carries: its own, or that of the driver\n * error it wraps, as a classified or a Drizzle query error does.\n */\nfunction errnoOf(error: unknown): number | undefined {\n for (let at: unknown = error, hops = 0; hops < 4; hops++) {\n if (typeof at !== \"object\" || at === null) return undefined;\n const errno: unknown = Reflect.get(at, \"errno\");\n if (typeof errno === \"number\") return errno;\n at = Reflect.get(at, \"cause\");\n }\n return undefined;\n}\n\n/**\n * MySQL database adapter for Nextly.\n *\n * Extends the base DrizzleAdapter to provide MySQL-specific functionality\n * using the mysql2 package.\n *\n * @remarks\n * MySQL has some limitations compared to PostgreSQL:\n * - No native RETURNING clause (requires INSERT then SELECT)\n * - No native ILIKE (uses LOWER() LIKE workaround)\n * - No native JSONB (uses JSON type)\n * - No array types\n * - Savepoints disabled for safety (MySQL has nested transaction quirks)\n *\n * @example\n * ```typescript\n * const adapter = new MySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * pool: { max: 20 },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport class MySqlAdapter extends DrizzleAdapter {\n // getDrizzle memoization: drizzle v1's constructor builds a relational\n // query builder per table in the relations config (~40 tables), and the\n // service layer resolves an instance on every db access — construct once\n // per relations object (identity-stable: the schema registry caches it\n // and hands out a NEW object on invalidation, which naturally misses\n // this cache and produces a fresh instance).\n private drizzleByRelations = new WeakMap<AnyRelations, unknown>();\n private drizzleBare: unknown;\n\n /**\n * The database dialect - always 'mysql' for this adapter.\n */\n readonly dialect = \"mysql\" as const;\n\n /**\n * Adapter configuration.\n */\n protected readonly config: MySqlAdapterConfig;\n\n /**\n * Connection pool instance.\n */\n private pool: Pool | null = null;\n\n /** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */\n private readonly sessions = new Set<Promise<unknown>>();\n\n /**\n * Connection state flag.\n */\n private connected = false;\n\n /**\n * Whether the server accepts `SELECT ... FOR SHARE`, read from its version\n * string at connect. Undefined until then, which callers treat as not known.\n */\n private sharedRowLock: boolean | undefined;\n private functionalIndexes: boolean | undefined;\n\n /**\n * Creates a new MySQL adapter instance.\n *\n * @param config - Adapter configuration\n */\n constructor(config: MySqlAdapterConfig) {\n super();\n this.config = config;\n }\n\n /**\n * Connect to the MySQL database.\n * Creates a connection pool using mysql2.\n *\n * @remarks\n * This method initializes the connection pool and verifies connectivity\n * by executing a simple query. It is idempotent - calling it multiple\n * times will not create multiple pools.\n *\n * @throws {DatabaseError} If connection fails\n */\n async connect(): Promise<void> {\n if (this.connected && this.pool) {\n return;\n }\n\n try {\n // Warned once, here, rather than on every session configuration the\n // standalone connections build: the configuration cannot change\n // between calls.\n this.warnOnDriverZoneDisagreement();\n const poolConfig = this.buildPoolConfig();\n const pool = mysql.createPool(poolConfig);\n // Pin the session time zone the moment each pooled connection opens.\n // Left unpinned, a session serves in whatever zone the server starts it\n // in — `SYSTEM` on the containers, anything on a self-hosted server —\n // and writes, window bounds and session-zone computations all follow\n // it. mysql2 emits `connection` when a physical connection is\n // established and BEFORE it reaches any caller, and each connection\n // queues its commands in order, so this SET has always run by the time\n // the first query handed the connection is sent; a replaced connection\n // is pinned again by the same listener.\n (pool.pool as unknown as PinnablePool).on(\"connection\", connection => {\n connection.query(\"SET SESSION time_zone = '+00:00'\", error => {\n // A session that could not be pinned must never be pooled: it\n // would serve in whatever zone it answers in.\n if (error) connection.destroy();\n });\n });\n // mysql2 queues a request for a connection without limit; it fails as\n // busy past the same bound a new connection gets to open.\n boundPoolWait(\n pool.pool as unknown as QueueingPool,\n poolConfig.connectTimeout ?? 0\n );\n // Cast to our Pool interface - mysql2's mixin types don't resolve properly\n this.pool = pool as unknown as Pool;\n\n // Verify connection with smoke test, then check dialect version.\n // Why: F17 hard-fails at connect on real MySQL <8.0 (no variant\n // token detected). Recognized variants (MariaDB, TiDB, Aurora,\n // PlanetScale, Vitess) log a warning via the adapter logger and\n // proceed. Truly unparseable strings hard-fail so users see the\n // issue at boot rather than mid-apply.\n const connection = await this.pool.getConnection();\n try {\n await connection.query(\"SELECT 1\");\n const { variant, functionalIndexes } = await checkDialectVersion(\n connection,\n \"mysql\",\n {\n // Why: route variant warnings through the adapter's logger so\n // users see a single, consistent log surface.\n onWarning: msg => this.config.logger?.warn?.(msg),\n }\n );\n // MariaDB and TiDB connect through this adapter too, and reject\n // `FOR SHARE`; the capability tells callers which lock they can take.\n this.sharedRowLock = acceptsForShare(variant);\n // Whether a unique index may key on an expression, which per-language uniqueness of a\n // translated value does.\n this.functionalIndexes = functionalIndexes;\n this.connected = true;\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection established\", {\n host: this.config.host ?? \"from URL\",\n database: this.config.database ?? \"from URL\",\n });\n }\n } finally {\n connection.release();\n }\n } catch (error) {\n // Clean up on failure\n if (this.pool) {\n await this.pool.end().catch(() => {});\n this.pool = null;\n }\n throw this.classifyError(error);\n }\n }\n\n /**\n * Disconnect from the MySQL database.\n * Gracefully closes the connection pool.\n *\n * @remarks\n * This method is idempotent - calling it multiple times is safe.\n * It waits for all connections to be released before shutting down,\n * including each running `withSession`'s connection of its own.\n */\n async disconnect(): Promise<void> {\n // Detach the pool FIRST, then drop the memoized drizzle instances —\n // this closes the repopulation window where a concurrent getDrizzle()\n // call during the (async) pool.end() could cache an instance wrapping\n // the closing pool.\n const pool = this.pool;\n this.pool = null;\n this.drizzleBare = undefined;\n this.drizzleByRelations = new WeakMap();\n if (!pool) {\n return;\n }\n\n try {\n // With the pool detached no session starts; those running end first.\n await Promise.allSettled([...this.sessions]);\n await pool.end();\n\n if (this.config.logger?.info) {\n this.config.logger.info(\"MySQL connection closed\");\n }\n } finally {\n this.connected = false;\n }\n }\n\n /**\n * Check if connected to the database.\n */\n isConnected(): boolean {\n return this.connected && this.pool !== null;\n }\n\n /**\n * Get connection pool statistics.\n * Returns null if not connected.\n *\n * @remarks\n * MySQL2 pool exposes different stats than pg:\n * - _allConnections: all connections\n * - _freeConnections: idle connections\n * - _connectionQueue: waiting requests\n */\n getPoolStats(): PoolStats | null {\n if (!this.pool) {\n return null;\n }\n\n // mysql2 Pool internal properties (cast to access internals)\n const poolInternal = this.pool as unknown as {\n pool?: {\n _allConnections?: { length: number };\n _freeConnections?: { length: number };\n _connectionQueue?: { length: number };\n };\n };\n\n const internal = poolInternal.pool;\n if (!internal) {\n return {\n total: 0,\n idle: 0,\n waiting: 0,\n active: 0,\n };\n }\n\n const total = internal._allConnections?.length ?? 0;\n const idle = internal._freeConnections?.length ?? 0;\n const waiting = internal._connectionQueue?.length ?? 0;\n\n return {\n total,\n idle,\n waiting,\n active: total - idle,\n };\n }\n\n /**\n * The connection as a health check reads it, on one pooled connection: the\n * server's version, timed for the round trip, then that session's\n * `Ssl_cipher` status, which names the cipher when the session's connection\n * is encrypted and is empty when it is not.\n *\n * @remarks\n * The status and the connection's own socket are weighed by\n * `encryptionOf`: behind a proxy the status can describe the proxy's own\n * connection to the server, while this connection's socket is TLS. The\n * socket is read as it is, never inferred from the `ssl` setting.\n *\n * Whether the database is on this machine is judged from the configured\n * address, which never leaves this method. Any failure answers `\"unknown\"`.\n */\n override async inspectConnection(): Promise<ConnectionInspection> {\n const local = this.isLocalHost(this.connectionHost());\n let connection: PoolConnection | undefined;\n try {\n connection = await this.ensurePool().getConnection();\n const started = performance.now();\n const [versionRows] = await connection.query<RowDataPacket[]>(\n \"SELECT VERSION() AS version\"\n );\n const roundTripMs = Math.max(0, Math.round(performance.now() - started));\n const [statusRows] = await connection.query<RowDataPacket[]>(\n \"SHOW SESSION STATUS LIKE 'Ssl_cipher'\"\n );\n const cipher: unknown = statusRows[0]?.Value;\n const version: unknown = versionRows[0]?.version;\n return {\n encryption: this.encryptionOf({\n session: typeof cipher === \"string\" ? cipher !== \"\" : undefined,\n ownSocketTls: this.ownSocketIsTls(connection),\n }),\n local,\n roundTripMs,\n ...(typeof version === \"string\" && version !== \"\"\n ? { serverVersion: version.split(\"-\")[0] }\n : {}),\n };\n } catch {\n return { encryption: \"unknown\", local };\n } finally {\n connection?.release();\n }\n }\n\n /**\n * The host this adapter connects to, as mysql2 resolves it: the connection\n * string's when one is set, else the `host` option, else `localhost`.\n * `undefined` when the connection string cannot be read.\n */\n private connectionHost(): string | undefined {\n const configured = this.config.url\n ? this.hostOfUrl(this.config.url)\n : (this.config.host ?? \"\");\n return configured === \"\" ? \"localhost\" : configured;\n }\n\n /**\n * Execute a raw SQL query.\n *\n * @param sql - SQL query string with ? placeholders\n * @param params - Query parameters\n * @returns Query results\n *\n * @throws {DatabaseError} If query execution fails\n */\n async executeQuery<T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> {\n const pool = this.ensurePool();\n const startTime = Date.now();\n\n try {\n // Outside the calling flow's transaction, if it has one (`notePoolAccess`).\n this.notePoolAccess(sql);\n const [rows] = await pool.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n\n // Log query if logger configured\n if (this.config.logger?.query) {\n const durationMs = Date.now() - startTime;\n this.config.logger.query(sql, params, durationMs);\n }\n\n return rows as T[];\n } catch (error) {\n throw this.classifyError(error, sql);\n }\n }\n\n /**\n * Execute work within a transaction.\n *\n * @param work - Function containing transactional operations\n * @param options - Transaction options (isolation level, timeout, retry)\n * @returns Result of the work function\n *\n * @remarks\n * MySQL transactions support isolation levels. Automatic retry is\n * implemented for deadlocks (error 1213) when `retryCount` is specified.\n *\n * Note: Savepoints are disabled in this adapter for safety due to\n * MySQL's quirks with nested transactions.\n *\n * A call made inside another transaction's work therefore joins it flat,\n * on its connection: its statements are the outer's. Its failure cannot be\n * undone alone, so it fails the whole transaction, even when the outer\n * catches it: the outer's COMMIT is refused, it rolls back, and it rejects\n * with the error that failed the joined call; any later joined call fails\n * with that error at once. A joined call keeps the outer's options and is\n * never retried itself. `retryWhen` is asked first, of every error that\n * ends an attempt, application ones included.\n */\n transaction<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options?: TransactionOptions\n ): Promise<T> {\n return this.openOrJoin(work, options, {\n run: (_attempt, openedAt) => this.runAttempt(work, options, openedAt),\n retryable: error => errnoOf(error) === DEADLOCK,\n describe: error => {\n const errno = errnoOf(error);\n return {\n label: errno === DEADLOCK ? \"deadlock\" : \"a retryable error\",\n detail: { errno },\n };\n },\n logger: this.config.logger,\n });\n }\n\n /**\n * Runs `work` on a connection of its own, opened for it with the pool's\n * connection settings, outside a transaction, each statement committing as\n * it runs.\n *\n * @remarks\n * Not a pooled connection: on a server whose sessions start with autocommit\n * off, one can hold a transaction an earlier caller left open, and turning\n * autocommit on would commit that caller's writes. A new connection holds\n * nothing. It does not count against `pool.max`.\n *\n * Each statement commits as it runs, whatever the server's own autocommit\n * default. With `strictSqlMode` the session runs in strict mode, as a\n * transaction given the same option does, so a statement that would cut a\n * value short fails and changes nothing. The connection is closed\n * afterwards, whether `work` finished or threw, so whatever `work` set on\n * its session (a temporary table, a session variable, a lock) ends with it.\n */\n override async withSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n // A precondition: before any connection is opened.\n this.refuseSessionInTransaction();\n // Connected first, as every other call requires.\n this.ensurePool();\n // Tracked before its connection opens: `disconnect` waits for it.\n const running = this.runSession(work, options);\n this.sessions.add(running);\n try {\n return await running;\n } finally {\n this.sessions.delete(running);\n }\n }\n\n /** `withSession`'s work, on a connection it opens and closes. */\n private async runSession<T>(\n work: (session: StatementSession) => Promise<T>,\n options?: SessionOptions\n ): Promise<T> {\n let connection: OwnConnection;\n try {\n // mysql2's connection already satisfies `OwnConnection`, so it is assigned as it comes.\n connection = await mysql.createConnection(this.buildConnectionConfig());\n } catch (error) {\n throw this.classifyError(error);\n }\n try {\n try {\n // A server whose sessions start with autocommit off would otherwise\n // hold every statement in a transaction nobody commits.\n await connection.query(\"SET SESSION autocommit = 1\");\n // The connection opened outside the pool, so the pool's `connection`\n // listener never pins it; its driver already parses in UTC, and a\n // session still in the server's zone would shift every value a\n // round-trip fails to cancel. The work below never runs unpinned.\n await connection.query(\"SET SESSION time_zone = '+00:00'\");\n if (options?.strictSqlMode) {\n // Nothing to put back: the connection is closed afterwards.\n await this.enterStrictMode(connection, {});\n }\n } catch (error) {\n throw this.classifyError(error);\n }\n return await work({\n execute: async <R = unknown>(\n statement: string,\n params: SqlParam[] = []\n ): Promise<R[]> => {\n const startTime = Date.now();\n try {\n const [rows] = await connection.query<RowDataPacket[]>(\n statement,\n params as unknown[]\n );\n if (this.config.logger?.query) {\n this.config.logger.query(\n statement,\n params,\n Date.now() - startTime\n );\n }\n return rows as R[];\n } catch (error) {\n throw this.classifyError(error, statement);\n }\n },\n });\n } finally {\n connection.destroy();\n }\n }\n\n /** One attempt at an outermost transaction, on a connection of its own. */\n private async runAttempt<T>(\n work: (tx: TransactionContext) => Promise<T>,\n options: TransactionOptions | undefined,\n openedAt: string | undefined\n ) {\n const pool = this.ensurePool();\n // A checkout, taken from the pool like `executeQuery`'s queries are.\n this.notePoolAccess();\n const connection = await pool.getConnection();\n // Set when the ROLLBACK failed (`returnTransactionConnection`).\n let rollbackFailed = false;\n // What `beginTransaction` changed on the session, to put back once the\n // transaction has ended. Filled in as each change lands, so a setup that\n // fails part way still puts back what it had already changed.\n const restore: NonNullable<SessionRestore> = {};\n const settled = await this.settleOutermost(\n {\n context: this.createTransactionContext(connection),\n join: this.joinSteps(connection),\n openedAt,\n begin: () => this.beginTransaction(connection, options, restore),\n commit: async () => {\n await connection.query(\"COMMIT\");\n },\n // A failure is recorded for the return and rethrown, so the base\n // knows the ROLLBACK is unconfirmed.\n rollback: async () => {\n await connection.query(\"ROLLBACK\").catch((rollbackError: unknown) => {\n rollbackFailed = true;\n throw rollbackError;\n });\n },\n },\n work,\n options\n );\n // Returned before anything held runs, so an effect that opens a\n // transaction takes one connection, not two.\n await returnTransactionConnection(connection, rollbackFailed, restore);\n return settled;\n }\n\n /**\n * How a call made inside a transaction on `connection` joins it: flat, on\n * the same connection, with nothing to send. Any failure inside it fails\n * the whole transaction, since no savepoint can undo the part alone.\n */\n private joinSteps(connection: PoolConnection) {\n const callbackConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n return this.joinStepsOn(\n () => this.createTransactionContext(connection),\n relations =>\n relations\n ? drizzle({ client: callbackConnection, relations })\n : drizzle({ client: callbackConnection }),\n context => ({\n context,\n begin() {},\n commit() {},\n rollback() {},\n dooms: () => true,\n })\n );\n }\n\n /**\n * Get MySQL database capabilities.\n *\n * @remarks\n * MySQL has some limitations:\n * - No JSONB (uses JSON)\n * - No arrays\n * - No native ILIKE\n * - No RETURNING clause\n * - Savepoints disabled for safety\n */\n getCapabilities(): DatabaseCapabilities {\n return {\n dialect: \"mysql\",\n supportsJsonb: false, // MySQL uses JSON, not JSONB\n supportsJson: true,\n supportsArrays: false, // MySQL doesn't support array types\n supportsGeneratedColumns: true, // MySQL 5.7.6+\n supportsFts: true, // MySQL has FULLTEXT indexes\n supportsIlike: false, // No native ILIKE, use LOWER() LIKE\n supportsReturning: false, // No RETURNING clause in MySQL\n supportsSavepoints: false, // Disabled for safety per approved approach\n supportsOnConflict: true, // ON DUPLICATE KEY UPDATE\n maxParamsPerQuery: 65535, // MySQL limit\n maxIdentifierLength: 64, // MySQL limit\n sharedRowLock: this.sharedRowLock, // Known once connected\n functionalIndexes: this.functionalIndexes, // Known once connected\n };\n }\n\n /**\n * Build a placeholder for MySQL (uses ? instead of $1, $2, etc.)\n *\n * @param _index - Parameter index (ignored for MySQL)\n * @returns The ? placeholder\n */\n protected buildPlaceholder(_index: number): string {\n return \"?\";\n }\n\n /**\n * Build multiple placeholders for MySQL.\n *\n * @param count - Number of placeholders needed\n * @param _startIndex - Starting index (ignored for MySQL)\n * @returns Comma-separated ? placeholders\n */\n protected buildPlaceholders(count: number, _startIndex: number = 0): string {\n return Array(count).fill(\"?\").join(\", \");\n }\n\n /**\n * Escape an identifier for MySQL (uses backticks instead of double quotes).\n *\n * @param identifier - The identifier to escape\n * @returns Escaped identifier with backticks\n */\n protected escapeIdentifier(identifier: string): string {\n // MySQL uses backticks for identifiers\n return `\\`${identifier.replace(/`/g, \"``\")}\\``;\n }\n\n // ============================================================\n // Protected Helper Methods\n // ============================================================\n\n /**\n * Ensures pool is connected and returns it.\n *\n * @throws {DatabaseError} If not connected\n */\n private ensurePool(): Pool {\n if (!this.pool) {\n throw createDatabaseError({\n kind: \"connection\",\n message: \"MySqlAdapter is not connected. Call connect() first.\",\n });\n }\n return this.pool;\n }\n\n /**\n * Return the typed Drizzle instance for MySQL.\n * Guarded for server-only usage and requires an active connection.\n *\n * @param schema - Optional schema for relational queries (db.query.*)\n * @returns Drizzle ORM instance wrapping the mysql2 pool connection\n * @throws {Error} If called in browser or not connected\n */\n getDrizzle<T = MySql2Database<AnyRelations>>(relations?: AnyRelations): T {\n if (typeof window !== \"undefined\") {\n throw new Error(\"getDrizzle() is server-only\");\n }\n const pool = this.ensurePool();\n if (!relations) {\n this.drizzleBare ??= this.poolDrizzle(pool);\n return this.drizzleBare as T;\n }\n let cached = this.drizzleByRelations.get(relations);\n if (!cached) {\n cached = this.poolDrizzle(pool, relations);\n this.drizzleByRelations.set(relations, cached);\n }\n return cached as T;\n }\n\n /**\n * A Drizzle handle on the pool, for `getDrizzle`.\n *\n * Its `transaction` runs on a connection this adapter checks out and\n * returns itself (`pinDrizzleTransactions`): destroyed rather than pooled\n * when no ROLLBACK could be confirmed on it. And it counts for\n * `transactionOpenInFlow`, as the adapter's does.\n */\n private poolDrizzle(pool: Pool, relations?: AnyRelations) {\n // drizzle v1's mysql2 driver takes the CALLBACK pool and connection,\n // which the mysql2/promise wrappers expose on `.pool` and `.connection`:\n // handed a wrapper, it throws (\"Cannot set properties of undefined\n // (setting 'supportBigNumbers')\"). The pre-v1 `mode` option is gone.\n const onClient = (client: CallbackPool | CallbackConnection) =>\n relations ? drizzle({ client, relations }) : drizzle({ client });\n // The handle's own queries go to the pool, so it is handed a view of the\n // pool that reports them (`observedPool`).\n const callbackPool = this.observedPool(\n (pool as unknown as { pool: CallbackPool }).pool,\n [\"query\", \"execute\", \"getConnection\"]\n );\n // Its transactions join the one the calling flow is inside, as the\n // adapter's `transaction()` does, and open one of this adapter's\n // otherwise (`joiningHandle`).\n return this.joiningHandle(\n onClient(callbackPool),\n async () => {\n const connection = await pool.getConnection();\n return {\n drizzle: onClient(\n (connection as unknown as { connection: CallbackConnection })\n .connection\n ),\n rollback: () => connection.query(\"ROLLBACK\"),\n release: dirty => {\n if (dirty) connection.destroy();\n else connection.release();\n },\n join: this.joinSteps(connection),\n };\n },\n relations\n );\n }\n\n /**\n * Warns when a caller's configured driver zone does not agree with the\n * pinned UTC session.\n *\n * mysql2's driver-level `timezone` decides how it parses the date strings\n * the pinned session answers with and how it binds raw Date parameters, so\n * a non-UTC spelling there would shift values a round-trip fails to\n * cancel. The configuration is aligned to the pin (`buildConnectionConfig`\n * sets UTC unconditionally, for pooled and standalone connections alike),\n * never rewritten silently: the caller configured a reading, and this is\n * where that reading is named as lost. Runs from `connect`, once per pool\n * build — the configuration cannot change between calls.\n */\n private warnOnDriverZoneDisagreement(): void {\n // mysql2's own default is `local`, so an unset configuration is judged\n // the same way: it agrees only on a UTC host.\n const requestedZone = this.config.timezone ?? \"local\";\n if (isUtcDriverZone(requestedZone)) return;\n this.config.logger?.warn?.(\n `Driver timezone \"${requestedZone}\" is not UTC; it is aligned to the pinned UTC session and no longer shifts reads or writes.`\n );\n }\n\n /**\n * Builds mysql2 Pool configuration from adapter config.\n */\n private buildPoolConfig(): PoolOptions {\n const config: PoolOptions = this.buildConnectionConfig();\n\n // Pool settings - mysql2 uses different property names\n config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;\n config.idleTimeout =\n this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;\n\n // A request waits for a connection when the pool is full. The queue has\n // no length limit; each wait in it is bounded instead (`boundPoolWait`).\n config.waitForConnections = true;\n config.queueLimit = 0;\n\n return config;\n }\n\n /**\n * Builds the settings every connection opens with, pooled or not, from\n * adapter config.\n */\n private buildConnectionConfig(): ConnectionOptions {\n const config: ConnectionOptions = this.buildAddressConfig();\n\n config.connectTimeout =\n this.config.pool?.connectionTimeoutMs ??\n DEFAULT_POOL_CONFIG.connectionTimeoutMs;\n\n // SSL configuration\n if (this.config.ssl) {\n config.ssl = this.buildSslConfig(this.config.ssl);\n }\n\n // MySQL-specific options. Every connection this adapter opens serves a\n // UTC session (see the pins in `connect` and `runSession`), so the\n // driver zone is pinned with them: mysql2 parses the date strings a\n // pinned session answers with and binds raw Date parameters, and a\n // driver in one zone beside a session in another shifts values every\n // time a round-trip fails to cancel. A configured zone that is not UTC\n // is aligned rather than refused — the pin is the deliberate boundary —\n // and `connect` warns the caller, once, that the shift they configured\n // is gone.\n config.timezone = \"Z\";\n\n if (this.config.charset) {\n config.charset = this.config.charset;\n }\n\n // Enable multiple statements if needed (disabled by default for security)\n config.multipleStatements = false;\n\n // Date handling\n config.dateStrings = false; // Return Date objects\n\n return config;\n }\n\n /** Where to connect: the connection string, or the explicit options. */\n private buildAddressConfig(): ConnectionOptions {\n if (this.config.url) return { uri: this.config.url };\n const { host, port, database, user, password } = this.config;\n const config: ConnectionOptions = { host, port, database, user, password };\n // An option left unset is left out, for mysql2's own default.\n for (const key of Object.keys(config) as (keyof ConnectionOptions)[]) {\n if (!config[key]) delete config[key];\n }\n return config;\n }\n\n /** mysql2's `ssl` option for the adapter's `ssl` setting. */\n private buildSslConfig(\n ssl: NonNullable<MySqlAdapterConfig[\"ssl\"]>\n ): ConnectionOptions[\"ssl\"] {\n if (typeof ssl === \"boolean\") {\n return ssl ? {} : undefined;\n }\n return {\n rejectUnauthorized: ssl.rejectUnauthorized,\n ca: ssl.ca,\n cert: ssl.cert,\n key: ssl.key,\n };\n }\n\n /**\n * Begins a transaction with the specified options, recording in `changed`\n * each session setting it changes, as it changes it, for `restoreSession`\n * to put back.\n */\n private async beginTransaction(\n connection: PoolConnection,\n options: TransactionOptions | undefined,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n // Set isolation level if specified (must be done before BEGIN)\n if (options?.isolationLevel) {\n const isolationMap: Record<string, string> = {\n \"read uncommitted\": \"READ UNCOMMITTED\",\n \"read committed\": \"READ COMMITTED\",\n \"repeatable read\": \"REPEATABLE READ\",\n serializable: \"SERIALIZABLE\",\n };\n const level = isolationMap[options.isolationLevel];\n if (level) {\n await connection.query(`SET TRANSACTION ISOLATION LEVEL ${level}`);\n }\n }\n\n // Set read-only mode if specified\n if (options?.readOnly) {\n await connection.query(\"SET TRANSACTION READ ONLY\");\n }\n\n // Begin the transaction\n await connection.query(\"START TRANSACTION\");\n\n // Set lock wait timeout if specified\n if (options?.timeoutMs) {\n // Read first, so the session's own value can be put back once the\n // transaction has ended (`SessionRestore`).\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout\"\n );\n const previous = Number(rows[0]?.timeout);\n // MySQL uses seconds for lock_wait_timeout\n const timeoutSeconds = Math.ceil(options.timeoutMs / 1000);\n await connection.query(\n `SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`\n );\n changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;\n }\n\n if (options?.strictSqlMode) {\n await this.enterStrictMode(connection, changed);\n }\n }\n\n /**\n * Strict mode for the rest of this connection's session: an INSERT, UPDATE\n * or ALTER that would cut a string to its column's length, or clamp a\n * number to its range, fails instead of applying with a warning. Added to\n * the session's own mode, so every other flag it had still holds, and\n * recorded in `changed` for `restoreSession` to put back.\n */\n private async enterStrictMode(\n connection: Queryable,\n changed: NonNullable<SessionRestore>\n ): Promise<void> {\n const [rows] = await connection.query<RowDataPacket[]>(\n \"SELECT @@SESSION.sql_mode AS mode\"\n );\n const previous: unknown = rows[0]?.mode;\n // Recorded before the SET: one the server applied but whose reply was\n // lost still has its mode put back.\n changed.sqlMode = typeof previous === \"string\" ? previous : null;\n await connection.query(\n \"SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))\"\n );\n }\n\n /**\n * Creates a TransactionContext for the given connection.\n *\n * @remarks\n * Note: Savepoint methods are not implemented (set to undefined)\n * as savepoints are disabled in this adapter per approved approach.\n */\n /**\n * Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL\n * and SQLite, on the tables where MySQL can keep that promise.\n *\n * MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`\n * fires on a collision with ANY unique key, and a failed insert does not\n * say reliably which one. So the conflict target must be the table's only\n * unique key apart from its primary key, and any other table is refused\n * before anything is written. On such a table the row is inserted first —\n * its validation and triggers apply, as they do before `ON CONFLICT`\n * elsewhere — and a duplicate is resolved by a locking read of the target\n * key, which sees the latest committed row rather than the transaction's\n * snapshot. The row found is kept as it is when there is nothing to update\n * (a no-op assignment would still run its update triggers), or updated by\n * its key. No row there means the collision was on the primary key, and it\n * is refused as `ON CONFLICT (target)` refuses it.\n *\n * The steps run in the caller's transaction, or in one of their own, since a\n * locking read locks nothing past its statement under autocommit. Rows are\n * read back by their primary key, so a trigger that rewrites the conflict\n * value still returns the row stored. The conflict columns must also be\n * given values: MySQL cannot look a row up by a key the database would only\n * fill in itself. A conflict is resolved on the caller's key value: a\n * BEFORE INSERT trigger that moves the key onto another row's value is\n * refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a\n * caller's transaction stays usable.\n */\n override async upsert<T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions,\n executor?: unknown\n ): Promise<T> {\n const tableObj = this.getTableObject(table) as MySqlTable | undefined;\n // Unknown table: the base method reports it as every CRUD method does.\n if (!tableObj) return super.upsert<T>(table, data, options, executor);\n let target: { where: SQL | undefined; nullKey: boolean };\n try {\n // Judged before anything else, so a refused call takes no connection.\n target = this.upsertTarget(tableObj, table, data, options);\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n // A locking read holds its lock only inside a transaction: under autocommit\n // it is released when its own statement ends, and the next statement may\n // run on another pooled connection. Without the caller's transaction, the\n // insert, the read and the update get one of their own, on one connection.\n if (executor === undefined) {\n return this.transaction(tx => tx.upsert<T>(table, data, options));\n }\n try {\n const db = executor as MySql2Database<AnyRelations>;\n // A conflict key holding NULL never conflicts: a unique index admits any\n // number of NULLs. It is a plain insert, read back by its primary key.\n if (target.nullKey) {\n return ((await this.insertRow(db, tableObj, data)) ?? data) as T;\n }\n return (await this.insertOrResolve(\n db,\n tableObj,\n data,\n options,\n target.where\n )) as T;\n } catch (error) {\n throw this.handleQueryError(error, \"upsert\", table);\n }\n }\n\n /**\n * What an upsert is keyed on: the target's condition, and whether its key\n * holds a NULL. Refuses a target this dialect cannot keep to, and a conflict\n * column left for the database to fill — MySQL cannot look a row up by a key\n * value only the database would supply.\n */\n private upsertTarget(\n tableObj: MySqlTable,\n table: string,\n data: Record<string, unknown>,\n options: UpsertOptions\n ): { where: SQL | undefined; nullKey: boolean } {\n // MySQL needs every conflict value whether or not it updates: the row is\n // found by them after a duplicate, so the keep-existing rule applies to\n // every call here.\n this.assertUpsertTarget(table, data, options, true);\n const key = this.conflictKey(tableObj, table, options.conflictColumns);\n const values = options.conflictColumns.map(column => data[column]);\n return {\n where: and(...key.map((column, i) => eq(column, values[i] as never))),\n nullKey: values.some(value => value === null),\n };\n }\n\n /**\n * The table's columns for a conflict target, refusing a target MySQL cannot\n * keep to: one that is not a primary or unique key, which an update could\n * spread over several rows, and one beside which the table has another\n * unique key, a collision on which MySQL would not tell apart.\n */\n private conflictKey(\n tableObj: MySqlTable,\n table: string,\n conflictColumns: readonly string[]\n ): Column[] {\n const record = tableObj as unknown as Record<string, Column | undefined>;\n const columns = conflictColumns.map(name => record[name]);\n const names = new Set(columns.map(column => column?.name));\n const isTarget = (key: readonly string[]) =>\n key.length === names.size && key.every(name => names.has(name));\n const { primary, unique } = keysOf(tableObj);\n // Every row this upsert returns is read back by its primary key, so a\n // table without one has no stable way to find the row it wrote.\n if (primary.length === 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`,\n });\n }\n const known = columns.every(column => column !== undefined);\n if (\n !known ||\n !(isTarget(primary) || unique.some(k => isTarget(k.columns)))\n ) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: the conflict columns (${conflictColumns.join(\", \")}) are not a primary or unique key of the table.`,\n });\n }\n const others = unique.filter(key => !isTarget(key.columns));\n if (others.length > 0) {\n throw createDatabaseError({\n kind: \"query\",\n message: `upsert on ${table}: MySQL cannot limit a duplicate to the conflict columns (${conflictColumns.join(\", \")}) while the table has other unique keys (${others.map(key => key.name).join(\", \")}). Upsert only on a table whose one unique key besides the primary key is the conflict target, or insert and update inside a transaction.`,\n });\n }\n return columns;\n }\n\n /** The update an upsert applies to an existing row: never its key. */\n private updateValues(\n data: Record<string, unknown>,\n options: UpsertOptions\n ): Record<string, unknown> {\n const key = new Set(options.conflictColumns);\n const update: Record<string, unknown> = {};\n for (const column of options.updateColumns ?? Object.keys(data)) {\n if (!key.has(column) && column in data) update[column] = data[column];\n }\n return update;\n }\n\n /** The row matching `where`, by a locking (current) read. */\n private async lockedRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n where: SQL | undefined\n ): Promise<unknown> {\n const rows = await db\n .select()\n .from(tableObj)\n .where(where)\n .limit(1)\n .for(\"update\");\n return rows[0];\n }\n\n /**\n * Insert the row, or resolve a duplicate on the target key. The insert is\n * read back by the primary key it reports, not by the conflict key: a\n * BEFORE INSERT trigger may have rewritten the conflict value, and the row\n * stored is the one to return.\n *\n * A duplicate is resolved when a row holds the TARGET key, whichever unique\n * key MySQL names in its error. PostgreSQL checks the target's index before\n * any other, so a row colliding on the target and on another key resolves\n * on the target there too; refusing it here because MySQL happened to check\n * the other index first would answer that case differently. A target row\n * committed by another transaction after this insert failed is the order in\n * which that transaction ran first, where PostgreSQL also resolves on it.\n */\n private async insertOrResolve(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>,\n options: UpsertOptions,\n where: SQL | undefined\n ): Promise<unknown> {\n let failure: unknown;\n try {\n const stored = await this.insertRow(db, tableObj, data);\n return stored ?? (await this.lockedRow(db, tableObj, where));\n } catch (error) {\n // Classified as everything else is, through the driver's own error\n // under Drizzle's wrapper (`classifyError`).\n if (this.classifyError(error).kind !== \"unique_violation\") throw error;\n failure = error;\n }\n const existing = await this.lockedRow(db, tableObj, where);\n // No row holds the target key: the collision was on another unique key,\n // refused as `ON CONFLICT (target)` refuses it, as the unique violation the\n // classifier reads it to be.\n if (existing === undefined) throw failure;\n const update = this.updateValues(data, options);\n if (Object.keys(update).length === 0) return existing;\n await db.update(tableObj).set(update).where(where);\n // Read back by the row's primary key as the update left it: the update may\n // set the key itself (every given column is updated by default, as `ON\n // CONFLICT DO UPDATE` does elsewhere), and a BEFORE UPDATE trigger may\n // have changed the conflict value the update was keyed on. A trigger that\n // changed the primary key instead leaves the conflict value standing, so\n // the row is looked for there next; one that changed both is not found.\n const identity = this.byIdentity(tableObj, {\n ...(existing as Record<string, unknown>),\n ...update,\n });\n const updated =\n identity === undefined\n ? undefined\n : await this.lockedRow(db, tableObj, identity);\n return updated ?? this.lockedRow(db, tableObj, where);\n }\n\n /**\n * Insert a row and read it back by its primary key: the value the caller\n * gave, or the one MySQL or the model generated, which `$returningId()`\n * reports and a given value it does not. Undefined when the table has no\n * primary key to read by.\n */\n private async insertRow(\n db: MySql2Database<AnyRelations>,\n tableObj: MySqlTable,\n data: Record<string, unknown>\n ): Promise<unknown> {\n const [generated] = (await db\n .insert(tableObj)\n .values(data as never)\n .$returningId()) as Record<string, unknown>[];\n // A key the caller gave wins; one given as undefined or null does not hide\n // the value MySQL generated for it.\n const given = Object.fromEntries(\n Object.entries(data).filter(\n ([, value]) => value !== undefined && value !== null\n )\n );\n const identity = this.byIdentity(tableObj, { ...generated, ...given });\n return identity === undefined\n ? undefined\n : this.lockedRow(db, tableObj, identity);\n }\n\n /**\n * A row's primary key as a condition, from a record keyed as the model's\n * properties are. Undefined when the table has no primary key or the record\n * lacks part of it.\n */\n private byIdentity(\n tableObj: MySqlTable,\n record: Record<string, unknown>\n ): SQL | undefined {\n const primary = new Set(keysOf(tableObj).primary);\n const parts = Object.entries(getColumns(tableObj)).filter(([, column]) =>\n primary.has(column.name)\n );\n if (\n parts.length === 0 ||\n parts.some(\n ([name]) => record[name] === undefined || record[name] === null\n )\n ) {\n return undefined;\n }\n return and(\n ...parts.map(([name, column]) => eq(column, record[name] as never))\n );\n }\n\n /** A table-level `primaryKey({ columns })`, read through the MySQL table config. */\n protected override compositePrimaryKey(\n tableObj: Record<string, unknown>\n ): object[] {\n return getTableConfig(\n tableObj as unknown as MySqlTable\n ).primaryKeys.flatMap(key => key.columns);\n }\n\n private createTransactionContext(\n connection: PoolConnection\n ): TransactionContext {\n // Bind a Drizzle instance to this transaction's checked-out connection so\n // the delegated CRUD methods run inside the transaction and see its\n // uncommitted rows. drizzle's mysql2 driver needs the underlying CALLBACK\n // connection, which the mysql2/promise wrapper exposes on `.connection`\n // (mirrors the `.pool` unwrap in getDrizzle()); getDrizzle() itself wraps\n // the pool, which would use a different connection. Built lazily and\n // memoized: transactions that use only raw execute/insert never construct\n // it.\n const txConnection = (\n connection as unknown as { connection: CallbackConnection }\n ).connection;\n const txHandles = this.transactionDrizzleHandles(relations =>\n relations\n ? drizzle({ client: txConnection, relations })\n : drizzle({ client: txConnection })\n );\n const txDb = txHandles.bare;\n return {\n drizzleWithRelations: txHandles.withRelations,\n\n execute: async <T = unknown>(\n sql: string,\n params: SqlParam[] = []\n ): Promise<T[]> => {\n const [rows] = await connection.query<RowDataPacket[]>(\n sql,\n params as unknown[]\n );\n return rows as T[];\n },\n\n // Run on the transaction-bound Drizzle instance rather than the pool, so\n // the statement is part of this transaction and sees its uncommitted rows.\n runStatement: async (statement: SQL): Promise<void> => {\n await txDb().execute(statement);\n },\n\n // mysql2 answers a `[rows, fields]` tuple; the transaction-bound instance\n // keeps the read inside this transaction so it sees its uncommitted writes.\n queryStatement: async <T = Record<string, unknown>>(\n statement: SQL\n ): Promise<T[]> => {\n const result = await txDb().execute(statement);\n // A tuple's first element is the rows. Anything else was not understood,\n // and must not reach a caller as \"there is nothing there\" — the same\n // reason the pooled `queryStatement` refuses rather than answering\n // empty.\n if (!Array.isArray(result)) {\n throw this.createDatabaseError(\n \"query\",\n \"Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.\",\n undefined\n );\n }\n return result[0] as unknown as T[];\n },\n\n lockRow: async (table: string, id: SqlParam): Promise<void> => {\n const idColumn = this.escapeIdentifier(\"id\");\n await connection.query(\n `SELECT ${idColumn} FROM ${this.escapeIdentifier(table)} ` +\n `WHERE ${idColumn} = ? FOR UPDATE`,\n [id] as unknown[]\n );\n },\n\n insert: async <T = unknown>(\n table: string,\n data: Record<string, unknown>,\n options?: InsertOptions\n ): Promise<T> => {\n // Refused before any statement, as the pooled insert is: a table this\n // instance never registered may not match what its code would write.\n const insertTableObj = this.resolvedTableObject(table);\n const mapped = this.mapRowToRawSql(insertTableObj, data);\n const columns = Object.keys(mapped);\n const values = Object.values(mapped);\n const placeholders = this.buildPlaceholders(values.length, 0);\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES (${placeholders})`;\n\n const [result] = await connection.query<ResultSetHeader>(sql, values);\n\n const ret = options?.returning;\n // No columns requested: skip the select-back reread entirely.\n if (Array.isArray(ret) && ret.length === 0) {\n return undefined as T;\n }\n\n // MySQL has no RETURNING; select the inserted row back. Project only the\n // requested columns so a large JSON snapshot is not read unless asked.\n // Each timestamp's wall clock, spelled out by the database. mysql2\n // turns one into a `Date` in the LOCAL zone before this code sees it,\n // and that conversion cannot be undone: a wall clock inside a\n // daylight-saving gap is normalized away.\n const aliases = this.dateWallClockAliases(insertTableObj, ret ?? \"*\");\n const spelled = aliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const projected =\n !ret || ret === \"*\"\n ? \"*\"\n : this.mapColumnNamesToSql(insertTableObj, ret)\n .map(c => this.escapeIdentifier(c))\n .join(\", \");\n const selectList = spelled ? `${projected}, ${spelled}` : projected;\n\n // Prefer the primary key: auto-increment via insertId, otherwise a\n // supplied id (manually-keyed tables like nextly_versions). Matching by\n // all values is a last resort because `col = NULL` never matches, so a\n // row with nullable columns would not be found by that path.\n const idValue = result.insertId ? result.insertId : mapped.id;\n if (idValue !== undefined) {\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE id = ?`,\n [idValue]\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n }\n\n const whereClauses = columns.map(\n c => `${this.escapeIdentifier(c)} = ?`\n );\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT ${selectList} FROM ${this.escapeIdentifier(table)} WHERE ${whereClauses.join(\" AND \")} LIMIT 1`,\n values\n );\n return this.mapRowFromRawSql(insertTableObj, rows[0] as T, aliases);\n },\n\n insertMany: async <T = unknown>(\n table: string,\n data: Record<string, unknown>[],\n options?: InsertOptions\n ): Promise<T[]> => {\n if (data.length === 0) return [];\n const retMany = options?.returning;\n const skipReread = Array.isArray(retMany) && retMany.length === 0;\n\n // Refused before any statement, as `insert` above is.\n const tableObj = this.resolvedTableObject(table);\n const mappedRecords = data.map(r => this.mapRowToRawSql(tableObj, r));\n const columns = Object.keys(mappedRecords[0]);\n const allValues: unknown[] = [];\n const valuesClauses: string[] = [];\n\n for (const record of mappedRecords) {\n const placeholders: string[] = [];\n for (const col of columns) {\n allValues.push(record[col]);\n placeholders.push(\"?\");\n }\n valuesClauses.push(`(${placeholders.join(\", \")})`);\n }\n\n const sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map(c => this.escapeIdentifier(c)).join(\", \")}) VALUES ${valuesClauses.join(\", \")}`;\n\n const [result] = await connection.query<ResultSetHeader>(\n sql,\n allValues\n );\n\n // For bulk insert, we need to SELECT the inserted rows\n // MySQL's insertId gives the first auto-increment ID\n if (!skipReread && result.insertId && result.affectedRows > 0) {\n const ids: number[] = [];\n for (let i = 0; i < result.affectedRows; i++) {\n ids.push(result.insertId + i);\n }\n const placeholders = ids.map(() => \"?\").join(\", \");\n const bulkAliases = this.dateWallClockAliases(tableObj, \"*\");\n const bulkSpelled = bulkAliases\n .map(\n a =>\n `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`\n )\n .join(\", \");\n const [rows] = await connection.query<RowDataPacket[]>(\n `SELECT *${bulkSpelled ? `, ${bulkSpelled}` : \"\"} FROM ${this.escapeIdentifier(table)} WHERE id IN (${placeholders})`,\n ids\n );\n return (rows as T[]).map(r =>\n this.mapRowFromRawSql(tableObj, r, bulkAliases)\n );\n }\n\n // Fallback: return empty if we can't determine inserted rows\n return [];\n },\n\n // Adapter-built, as `insert` above is; `transactionUpdate` says why.\n // A column the model does not declare binds natively, as every value\n // on this path's insert does.\n update: this.transactionUpdate(\n txDb,\n async statement => {\n // No RETURNING on MySQL: nothing to report, the read-back is by\n // the caller's predicate.\n await txDb().execute(statement);\n return undefined;\n },\n // Structured values as JSON text: mysql2's formatter spells an object\n // as `key = value` pairs and an array as a value list, neither JSON.\n value => this.bindUnmodeledStructuredAsJson(value)\n ),\n\n ...this.createTransactionForwarders(txDb),\n\n // Savepoints disabled per approved approach\n savepoint: undefined,\n rollbackToSavepoint: undefined,\n releaseSavepoint: undefined,\n };\n }\n\n /**\n * Classifies a MySQL error into a DatabaseError.\n *\n * @param error - Original error from mysql2\n * @param sql - SQL statement that caused the error (optional)\n * @returns DatabaseError with proper classification\n */\n protected override classifyError(\n error: unknown,\n sql?: string\n ): DatabaseError {\n // Why short-circuit on existing DatabaseError: F17's\n // UnsupportedDialectVersionError is already a typed DatabaseError with\n // kind: \"unsupported_version\" plus detectedVersion/requiredVersion\n // fields. Re-wrapping it here would erase those fields and re-tag it\n // as kind: \"unknown\".\n if (isDatabaseError(error)) return error;\n\n // Classified from mysql2's own error, which Drizzle puts on `cause` when it\n // wraps a failed query (`driverErrorIn`); it is the one carrying `errno`.\n const mysqlError = this.driverErrorIn<{\n errno?: number;\n code?: string;\n sqlState?: string;\n message?: string;\n sql?: string;\n cause?: unknown;\n }>(error, candidate => typeof candidate.errno === \"number\");\n\n // Determine error kind from MySQL error number\n const kind: DatabaseErrorKind =\n (mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno]) || \"unknown\";\n\n // Build error message\n let message = mysqlError.message ?? String(error);\n if (sql && kind === \"query\") {\n message = `Query failed: ${message}`;\n }\n\n return createDatabaseError({\n kind,\n message,\n code: mysqlError.code ?? mysqlError.errno?.toString(),\n detail: mysqlError.sql,\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n\n/** A table's keys by database column name, as its model declares them. */\ninterface TableKeys {\n primary: string[];\n unique: { name: string; columns: string[] }[];\n}\n\nconst TABLE_KEYS = new WeakMap<MySqlTable, TableKeys>();\n\n/**\n * A table's primary key and its unique keys other than the primary key, read\n * from its model once and kept for the table's lifetime: a model is not\n * changed in place, a new schema brings new table objects.\n */\nfunction keysOf(tableObj: MySqlTable): TableKeys {\n const cached = TABLE_KEYS.get(tableObj);\n if (cached) return cached;\n const config = getTableConfig(tableObj);\n const primary = [\n ...config.columns.filter(column => column.primary),\n ...config.primaryKeys.flatMap(key => key.columns),\n ].map(column => column.name);\n const unique = [\n ...config.columns\n .filter(column => column.isUnique && !column.primary)\n .map(column => ({\n // Drizzle's own name for an unnamed column-level unique key.\n name: column.uniqueName ?? `${config.name}_${column.name}_unique`,\n columns: [column.name],\n })),\n ...config.uniqueConstraints.map(constraint => ({\n name: constraint.name,\n columns: constraint.columns.map(column => column.name),\n })),\n ...config.indexes\n .filter(index => index.config.unique === true)\n .map(index => ({\n name: index.config.name,\n columns: (index.config.columns as { name: string }[]).map(\n column => column.name\n ),\n })),\n ];\n const keys = { primary, unique };\n TABLE_KEYS.set(tableObj, keys);\n return keys;\n}\n\n/**\n * Create a MySQL database adapter.\n *\n * @param config - MySQL adapter configuration\n * @returns A new MySqlAdapter instance\n *\n * @example\n * ```typescript\n * // Simple usage with URL\n * const adapter = createMySqlAdapter({\n * url: 'mysql://user:pass@localhost:3306/mydb',\n * });\n *\n * // Full configuration\n * const adapter = createMySqlAdapter({\n * url: process.env.DATABASE_URL!,\n * pool: {\n * min: 2,\n * max: 20,\n * idleTimeoutMs: 30000,\n * connectionTimeoutMs: 10000,\n * },\n * ssl: {\n * rejectUnauthorized: true,\n * },\n * });\n *\n * await adapter.connect();\n * ```\n */\nexport function createMySqlAdapter(config: MySqlAdapterConfig): MySqlAdapter {\n return new MySqlAdapter(config);\n}\n\n/**\n * Type guard to check if a value is a MySqlAdapter.\n *\n * @param value - Value to check\n * @returns True if value is a MySqlAdapter instance\n *\n * @example\n * ```typescript\n * if (isMySqlAdapter(adapter)) {\n * // TypeScript knows adapter is MySqlAdapter\n * console.log('Using MySQL');\n * }\n * ```\n */\nexport function isMySqlAdapter(value: unknown): value is MySqlAdapter {\n return value instanceof MySqlAdapter;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextlyhq/adapter-mysql",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.75",
|
|
4
4
|
"description": "MySQL database adapter for Nextly - extends @nextlyhq/adapter-drizzle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"node": "^22.12.0 || >=24.0.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@nextlyhq/adapter-drizzle": "0.0.2-alpha.
|
|
24
|
+
"@nextlyhq/adapter-drizzle": "0.0.2-alpha.75",
|
|
25
25
|
"drizzle-orm": "1.0.0-rc.4",
|
|
26
26
|
"mysql2": "^3.23.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.
|
|
29
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.75",
|
|
30
30
|
"@vitest/coverage-v8": "^5.0.5",
|
|
31
31
|
"@vitest/ui": "^5.0.5",
|
|
32
32
|
"mysql2": "^3.23.1",
|