@vibeorm/adapter-mysql 2.0.0-alpha.9 → 2.0.0
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/README.md +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +440 -22
- package/dist/index.js.map +8 -5
- package/dist/placeholders.d.ts +70 -0
- package/dist/placeholders.d.ts.map +1 -0
- package/dist/savepoint-gate.d.ts +86 -0
- package/dist/savepoint-gate.d.ts.map +1 -0
- package/dist/transaction-budget.d.ts +144 -0
- package/dist/transaction-budget.d.ts.map +1 -0
- package/dist/transaction-sql.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts", "../src/acquire.ts", "../src/errors.ts", "../src/results.ts", "../src/savepoints.ts", "../src/transaction-sql.ts"],
|
|
3
|
+
"sources": ["../src/index.ts", "../src/acquire.ts", "../src/errors.ts", "../src/placeholders.ts", "../src/results.ts", "../src/transaction-budget.ts", "../src/savepoint-gate.ts", "../src/savepoints.ts", "../src/transaction-sql.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @vibeorm/adapter-mysql — mysql2 adapter for VibeORM v2.\n *\n * Runs on Bun and Node over `mysql2/promise`. Owns pooling, transactions\n * (nested → savepoints on the same pinned connection) and driver-error\n * mapping. Only `VibeError` ever escapes.\n *\n * SAME-CONNECTION GUARANTEE (board #7): `transaction()` checks ONE connection\n * out of the pool and every operation on the callback's adapter — `execute`,\n * `executeUnsafe`, nested `transaction` — runs on that same connection until\n * commit/rollback. That pinning is what makes `LAST_INSERT_ID()` sound: the\n * runtime wraps DB-assigned-key creates in `adapter.transaction` and issues\n * `SELECT LAST_INSERT_ID() AS id` via `executeUnsafe`, and LAST_INSERT_ID() is\n * per-connection state in MySQL — on any other connection it would answer for\n * someone else's INSERT.\n *\n * Driver configuration is pinned to the runtime's MYSQL_CODECS contract\n * (packages/runtime/src/codecs.ts) — see {@link MysqlPoolConfig}.\n */\n\nimport type { DatabaseAdapter, QueryResult, TransactionOptions, WireFidelity } from \"@vibeorm/runtime\";\nimport type { Dialect, Provider } from \"@vibeorm/schema\";\nimport { internalError } from \"@vibeorm/schema\";\nimport { createPool as mysql2CreatePool } from \"mysql2/promise\";\nimport { MYSQL_DEFAULT_CONNECTION_LIMIT, acquireWithTimeout } from \"./acquire.ts\";\nimport { mapMysqlDriverError, runMapped } from \"./errors.ts\";\nimport { normalizeDriverResult } from \"./results.ts\";\nimport { createSavepointCounter, nextSavepointName, type SavepointCounter } from \"./savepoints.ts\";\nimport {\n RESET_MAX_EXECUTION_TIME_SQL,\n classifyRawTransactionControl,\n maxExecutionTimeStatement,\n refuseNestedTransactionOptions,\n setIsolationLevelStatement,\n} from \"./transaction-sql.ts\";\n\n// ─── Minimal mysql2 surface ───────────────────────────────────────\n\n/** The parts of a mysql2 `PoolConnection` this adapter uses. */\nexport type MysqlConnectionLike = {\n /** Binary protocol — prepared statements, cached per connection by mysql2. */\n execute(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n /** Text protocol — raw path and transaction-control statements. */\n query(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n beginTransaction(): Promise<void>;\n commit(): Promise<void>;\n rollback(): Promise<void>;\n ping(): Promise<void>;\n release(): void;\n destroy(): void;\n};\n\n/** The parts of a mysql2 promise `Pool` this adapter uses. */\nexport type MysqlPoolLike = {\n execute(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n query(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n getConnection(): Promise<MysqlConnectionLike>;\n end(): Promise<void>;\n};\n\n/** Anything that can run a statement: the pool itself or a pinned connection. */\ntype Queryable = Pick<MysqlConnectionLike, \"execute\" | \"query\">;\n\n// ─── Pool config ──────────────────────────────────────────────────\n\n/**\n * The exact configuration this adapter hands to `mysql2.createPool`. The\n * literal-typed fields are pinned to the runtime's MYSQL_CODECS contract and\n * are not user-overridable:\n *\n * - `decimalNumbers: false` — DECIMAL comes back as a string; the Decimal\n * codec keeps it a string (no float rounding) and trims the fixed-scale\n * padding MySQL reports (`DECIMAL(65, 30)` → `\"42.420…00\"` → `\"42.42\"`).\n * - `supportBigNumbers: true` + `bigNumberStrings: true` — BIGINT (and\n * DECIMAL) always come back as strings, never a precision-lossy JS number;\n * the BigInt codec decodes strings exactly.\n * - `dateStrings: false` — DATETIME comes back as a JS `Date`; the DateTime\n * codec passes `Date` through in both directions.\n * - `jsonStrings: false` — JSON columns come back already parsed, which is\n * what the Json codec's decode expects from mysql2.\n * - `namedPlaceholders: false` — the mysql dialect renders positional `?`.\n * - `multipleStatements` defaults to false: the ORM never batches, and\n * @vibeorm/migrate's mysql runner applies DDL per-statement anyway (DDL\n * auto-commits, so per-statement progress tracking needs it).\n */\nexport type MysqlPoolConfig = {\n readonly uri?: string;\n readonly host?: string;\n readonly port?: number;\n readonly user?: string;\n readonly password?: string;\n readonly database?: string;\n readonly connectionLimit?: number;\n readonly charset?: string;\n readonly timezone: string;\n readonly decimalNumbers: false;\n readonly supportBigNumbers: true;\n readonly bigNumberStrings: true;\n readonly dateStrings: false;\n readonly jsonStrings: false;\n readonly namedPlaceholders: false;\n readonly multipleStatements: boolean;\n};\n\n// ─── Options ──────────────────────────────────────────────────────\n\n/**\n * Options for {@link mysqlAdapter}. Point it at a connection URL **or** at\n * discrete `host`/`port`/`user`/`password`/`database` fields (mysql2 accepts\n * both; when both are given the discrete fields win — mysql2 merges the parsed\n * URL underneath explicit config), or hand it a pool you already own.\n */\nexport type MysqlAdapterOptions = {\n /** MySQL connection URL (`mysql://user:pw@host:3306/db`). Defaults to `process.env.MYSQL_URL`. */\n readonly url?: string;\n readonly host?: string;\n readonly port?: number;\n readonly user?: string;\n readonly password?: string;\n readonly database?: string;\n /**\n * An existing mysql2 promise pool to use instead of creating one. The\n * adapter never ends a pool it did not create, so `disconnect()` is a no-op\n * in this mode. The pool MUST be configured to the codec contract\n * ({@link MysqlPoolConfig}) — the adapter cannot verify it.\n */\n readonly pool?: MysqlPoolLike;\n /** Maximum pooled connections (mysql2 default 10). Ignored when `pool` is given. */\n readonly connectionLimit?: number;\n /**\n * mysql2 `timezone`, default `\"Z\"`: the codec table stores DateTime as UTC\n * in `DATETIME(3)` columns, and DATETIME has no zone of its own — with\n * `\"Z\"` mysql2 serializes outgoing `Date`s as UTC and parses incoming\n * DATETIME text as UTC, so the instant round-trips regardless of the server\n * or process time zone. mysql2's own default (`\"local\"`) would silently\n * shift values by the process offset.\n */\n readonly timezone?: string;\n /** Connection charset/collation (mysql2 default `utf8mb4_general_ci`). */\n readonly charset?: string;\n /**\n * Milliseconds to wait for a pool connection before refusing with a\n * `VibeError` (`VIBE_ADAPTER`, `meta.reason: \"acquire-timeout\"`).\n *\n * **Unset (the default) keeps mysql2's own behaviour**: the pool is created\n * with `waitForConnections: true` and no time bound, so a caller whose\n * connections are all busy QUEUES until one frees. Setting this bounds EVERY\n * pool checkout the adapter makes — the caller gets a coded refusal instead\n * of an unbounded wait.\n *\n * Adapter-owned: mysql2 has no acquire timeout at all (its pool knobs are\n * `waitForConnections` and `queueLimit`, which bounds queue DEPTH, not time),\n * so this value is never passed to `mysql2.createPool`.\n *\n * **Not mysql2's `connectTimeout`**, which is the deadline for establishing a\n * NEW connection rather than for waiting on a busy pool. The equivalent\n * option on `@vibeorm/adapter-pg` is `connectionTimeout`; the name differs\n * here precisely because mysql2 already uses that word for something else.\n */\n readonly acquireTimeout?: number;\n /**\n * Allow multiple statements per `executeUnsafe` call (default false). Leave\n * it off: the ORM never batches, @vibeorm/migrate applies DDL per statement,\n * and enabling it widens the SQL-injection blast radius of raw queries.\n */\n readonly multipleStatements?: boolean;\n /**\n * Pool factory — a test seam. Defaults to `mysql2/promise`'s `createPool`;\n * tests inject a recording fake here to assert the exact config the adapter\n * builds. Ignored when `pool` is given.\n */\n readonly createPool?: (config: MysqlPoolConfig) => MysqlPoolLike;\n};\n\n// ─── Adapter ──────────────────────────────────────────────────────\n\nconst DIALECT: Dialect = \"mysql\";\nconst PROVIDER: Provider = \"mysql\";\n\n/**\n * mysql2 wire (board #30, live-verified on both execute paths, 8.4):\n * datetime(3) → `Date` (under the adapter's pinned `timezone: \"Z\"`), JSON →\n * parsed values, int/double → numbers. (BIGINT arrives as a string and\n * TINYINT(1) as 0/1 — BigInt/Boolean decoding stays.)\n */\nconst WIRE: WireFidelity = Object.freeze({ dateTime: \"native\", json: \"parsed\", numbers: \"native\" } as const);\n\nfunction defaultCreatePool(config: MysqlPoolConfig): MysqlPoolLike {\n // Single cast seam: mysql2's Pool satisfies MysqlPoolLike structurally, but\n // its generic overloads don't reduce to our plain signatures.\n return mysql2CreatePool(config as Parameters<typeof mysql2CreatePool>[0]) as unknown as MysqlPoolLike;\n}\n\n/** Scalar-array params must never reach this adapter — the dialect expands IN. */\nfunction throwOnArrayParam(values: unknown[]): never {\n throw internalError({\n message:\n \"formatArrayParam called on the mysql adapter — the mysql dialect expands scalar-list parameters into IN (?, ?, …) placeholders, so the runtime should have expanded this array before binding\",\n meta: { length: values.length },\n });\n}\n\n/**\n * Create a VibeORM adapter backed by mysql2.\n *\n * @example\n * ```ts\n * const adapter = mysqlAdapter({ url: process.env.MYSQL_URL, connectionLimit: 20 });\n * await adapter.connect();\n * const rows = await adapter.execute({ text: \"SELECT * FROM `User` WHERE `id` = ?\", values: [1] });\n * ```\n *\n * @example Discrete fields, or reusing a pool you already own\n * ```ts\n * const adapter = mysqlAdapter({ host: \"127.0.0.1\", port: 3306, user: \"app\", password: \"pw\", database: \"app\" });\n * const shared = mysqlAdapter({ pool: existingPool });\n * ```\n *\n * ORM path (`execute`) uses mysql2's binary protocol (`pool.execute`), which\n * prepares statements and caches them per connection keyed by statement text\n * (mysql2's `maxPreparedStatements` LRU, default 16000). Raw path\n * (`executeUnsafe`) uses the text protocol (`pool.query`) — no statement\n * cache, and DDL (which the binary protocol cannot prepare) works.\n *\n * Parameters arrive PRE-ENCODED by the runtime codec table: Json is already a\n * string, DateTime is a `Date` (mysql2 serializes it per `timezone`), BigInt\n * is a string. The adapter forwards values verbatim — no second conversion.\n */\nexport function mysqlAdapter(options?: MysqlAdapterOptions): DatabaseAdapter {\n let pool: MysqlPoolLike | null = null;\n let ownsPool = false;\n\n /**\n * The pinned connection of an OPEN raw transaction — session affinity for\n * the raw path. A raw `BEGIN` … `COMMIT` sequence over `executeUnsafe` is\n * sound only when every statement in between rides the SAME pooled\n * connection, so a pure `BEGIN` checks one out, raw statements ride it, and\n * the matching `COMMIT`/`ROLLBACK` releases it (same semantics as the pg and\n * bun adapters). The ORM path (`execute`) and `transaction()` stay on the\n * pool. Concurrent raw transactions on one adapter are unsupported.\n */\n let rawSession: MysqlConnectionLike | null = null;\n\n const acquireTimeoutMs = options?.acquireTimeout;\n\n /**\n * Effective pool size for the refusal message. Unknown — and reported as\n * such — when the caller supplied the pool, since `connectionLimit` is\n * ignored in that mode.\n */\n const poolSize: number | undefined =\n options?.pool === undefined ? (options?.connectionLimit ?? MYSQL_DEFAULT_CONNECTION_LIMIT) : undefined;\n\n function getPool(): MysqlPoolLike {\n if (pool !== null) return pool;\n\n if (options?.pool !== undefined) {\n pool = options.pool;\n ownsPool = false;\n return pool;\n }\n\n const config: MysqlPoolConfig = {\n uri: options?.url ?? process.env.MYSQL_URL,\n host: options?.host,\n port: options?.port,\n user: options?.user,\n password: options?.password,\n database: options?.database,\n connectionLimit: options?.connectionLimit,\n charset: options?.charset,\n timezone: options?.timezone ?? \"Z\",\n decimalNumbers: false,\n supportBigNumbers: true,\n bigNumberStrings: true,\n dateStrings: false,\n jsonStrings: false,\n namedPlaceholders: false,\n multipleStatements: options?.multipleStatements ?? false,\n };\n pool = (options?.createPool ?? defaultCreatePool)(config);\n ownsPool = true;\n return pool;\n }\n\n /**\n * The ONE place a connection is checked out. Without `acquireTimeout` this is\n * mysql2's own unbounded checkout, byte for byte; with it, the wait is\n * bounded and a caller that cannot be served refuses instead of queueing.\n * `runMapped` maps driver failures — the timeout error is already a\n * `VibeError` and passes through untouched (errors.ts:121).\n */\n async function acquireConnection(): Promise<MysqlConnectionLike> {\n const active = getPool();\n if (acquireTimeoutMs === undefined) {\n return runMapped(() => active.getConnection());\n }\n return runMapped(() => acquireWithTimeout({ pool: active, timeoutMs: acquireTimeoutMs, poolSize }));\n }\n\n async function runExecute(params: {\n queryable: Queryable;\n text: string;\n values: unknown[];\n }): Promise<Record<string, unknown>[]> {\n const [result] = await runMapped(() => params.queryable.execute(params.text, params.values));\n return normalizeDriverResult({ result }).rows;\n }\n\n async function runExecuteUnsafe(params: {\n queryable: Queryable;\n text: string;\n values?: unknown[];\n }): Promise<QueryResult> {\n const [result] = await runMapped(() => params.queryable.query(params.text, params.values));\n return normalizeDriverResult({ result });\n }\n\n /**\n * Adapter bound to the ONE pinned connection of an open transaction — the\n * same-connection guarantee. Nested `transaction()` calls open savepoints on\n * that connection, naming them from the counter shared across the whole\n * top-level transaction so siblings never collide.\n */\n function createConnectionAdapter(params: {\n conn: MysqlConnectionLike;\n savepointCounter: SavepointCounter;\n }): DatabaseAdapter {\n const { conn, savepointCounter } = params;\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n\n async execute(execParams) {\n return runExecute({ queryable: conn, ...execParams });\n },\n\n async executeUnsafe(execParams) {\n return runExecuteUnsafe({ queryable: conn, ...execParams });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n nestedOptions?: TransactionOptions,\n ): Promise<T> {\n refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });\n const savepoint = nextSavepointName({ counter: savepointCounter });\n await runMapped(() => conn.query(`SAVEPOINT ${savepoint}`));\n try {\n const result = await fn(createConnectionAdapter({ conn, savepointCounter }));\n await runMapped(() => conn.query(`RELEASE SAVEPOINT ${savepoint}`));\n return result;\n } catch (error) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await conn.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);\n } catch {\n /* rollback is best-effort */\n }\n throw error;\n }\n },\n\n async connect(): Promise<void> {\n // Already connected — this adapter is bound to a pinned connection.\n },\n\n async disconnect(): Promise<void> {\n // The pool owns the connection lifecycle; releasing happens at the\n // top-level transaction boundary.\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return throwOnArrayParam(values);\n },\n };\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n\n async execute(params) {\n // Default path: mysql2's `pool.execute` does its own implicit checkout.\n // With `acquireTimeout` set that checkout has to be visible to be\n // bounded, so the statement is routed over an explicit connection\n // instead (mysql2 caches prepared statements per connection either way).\n if (acquireTimeoutMs === undefined) {\n return runExecute({ queryable: getPool(), ...params });\n }\n const conn = await acquireConnection();\n try {\n return await runExecute({ queryable: conn, ...params });\n } finally {\n conn.release();\n }\n },\n\n async executeUnsafe(params) {\n const control = classifyRawTransactionControl({ text: params.text });\n if (rawSession !== null) {\n if (control === \"close\") {\n const session = rawSession;\n rawSession = null;\n try {\n return await runExecuteUnsafe({ queryable: session, ...params });\n } finally {\n session.release();\n }\n }\n return runExecuteUnsafe({ queryable: rawSession, ...params });\n }\n if (control === \"open\") {\n const conn = await acquireConnection();\n try {\n const result = await runExecuteUnsafe({ queryable: conn, ...params });\n rawSession = conn;\n return result;\n } catch (error) {\n conn.release();\n throw error;\n }\n }\n // Default path: mysql2's `pool.query` checks a connection out\n // implicitly. Same reroute as `execute()` above, for the same reason —\n // a checkout has to be visible to be bounded, so with `acquireTimeout`\n // set the raw statement runs on an explicit connection too.\n if (acquireTimeoutMs === undefined) {\n return runExecuteUnsafe({ queryable: getPool(), ...params });\n }\n const conn = await acquireConnection();\n try {\n return await runExecuteUnsafe({ queryable: conn, ...params });\n } finally {\n conn.release();\n }\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n // Validate the timeout before taking a connection, so a bad option fails\n // fast instead of wasting a checkout.\n const timeoutText =\n transactionOptions?.timeout === undefined\n ? undefined\n : maxExecutionTimeStatement({ timeout: transactionOptions.timeout });\n\n const conn = await acquireConnection();\n try {\n // Session-scoped SELECT bound — see transaction-sql.ts for the loud\n // \"SELECT statements only\" caveat. Reset in finally below.\n if (timeoutText !== undefined) {\n await runMapped(() => conn.query(timeoutText));\n }\n // Applies to the NEXT transaction in the session → must precede begin.\n const isolationLevel = transactionOptions?.isolationLevel;\n if (isolationLevel !== undefined) {\n const isolationText = setIsolationLevelStatement({ isolationLevel });\n await runMapped(() => conn.query(isolationText));\n }\n await runMapped(() => conn.beginTransaction());\n try {\n const result = await fn(createConnectionAdapter({ conn, savepointCounter: createSavepointCounter() }));\n await runMapped(() => conn.commit());\n return result;\n } catch (error) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await conn.rollback();\n } catch {\n /* rollback is best-effort */\n }\n throw error;\n }\n } finally {\n let poisoned = false;\n if (timeoutText !== undefined) {\n try {\n await conn.query(RESET_MAX_EXECUTION_TIME_SQL);\n } catch {\n poisoned = true;\n }\n }\n // A connection whose session default could not be restored must never\n // return to the pool with a lowered MAX_EXECUTION_TIME.\n if (poisoned) {\n conn.destroy();\n } else {\n conn.release();\n }\n }\n },\n\n async connect(): Promise<void> {\n const conn = await acquireConnection();\n try {\n await runMapped(() => conn.ping());\n } finally {\n conn.release();\n }\n },\n\n async disconnect(): Promise<void> {\n if (rawSession !== null) {\n // An abandoned raw transaction must not leak an in-transaction\n // connection; roll it back best-effort before the pool ends.\n const session = rawSession;\n rawSession = null;\n try {\n await session.query(\"ROLLBACK\");\n } catch {\n /* the pool is closing; the session is going away regardless */\n }\n session.release();\n }\n if (pool !== null && ownsPool) {\n const closing = pool;\n pool = null;\n await runMapped(() => closing.end());\n }\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return throwOnArrayParam(values);\n },\n };\n}\n\n// ─── Re-exports (driver-error table, helpers) ─────────────────────\n\nexport {\n DEFAULT_ACQUIRE_TIMERS,\n MYSQL_DEFAULT_CONNECTION_LIMIT,\n acquireTimeoutError,\n acquireWithTimeout,\n} from \"./acquire.ts\";\nexport type { AcquireSource, AcquireTimers, ReleasableConnection, TimerHandle } from \"./acquire.ts\";\nexport {\n MYSQL_ERRNO_ERROR_CODES,\n MYSQL_ERRNO_REASONS,\n mapMysqlDriverError,\n mysqlErrorErrno,\n runMapped,\n} from \"./errors.ts\";\nexport { isResultSetHeader, normalizeDriverResult } from \"./results.ts\";\nexport { createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\nexport type { SavepointCounter } from \"./savepoints.ts\";\nexport {\n ISOLATION_LEVEL_SQL,\n RESET_MAX_EXECUTION_TIME_SQL,\n classifyRawTransactionControl,\n maxExecutionTimeStatement,\n refuseNestedTransactionOptions,\n setIsolationLevelStatement,\n} from \"./transaction-sql.ts\";\nexport type { RawTransactionControl } from \"./transaction-sql.ts\";\n",
|
|
5
|
+
"/**\n * @vibeorm/adapter-mysql — mysql2 adapter for VibeORM v2.\n *\n * Runs on Bun and Node over `mysql2/promise`. Owns pooling, transactions\n * (nested → savepoints on the same pinned connection) and driver-error\n * mapping. Only `VibeError` ever escapes.\n *\n * SAME-CONNECTION GUARANTEE (board #7): `transaction()` checks ONE connection\n * out of the pool and every operation on the callback's adapter — `execute`,\n * `executeUnsafe`, nested `transaction` — runs on that same connection until\n * commit/rollback. That pinning is what makes `LAST_INSERT_ID()` sound: the\n * runtime wraps DB-assigned-key creates in `adapter.transaction` and issues\n * `SELECT LAST_INSERT_ID() AS id` via `executeUnsafe`, and LAST_INSERT_ID() is\n * per-connection state in MySQL — on any other connection it would answer for\n * someone else's INSERT.\n *\n * Driver configuration is pinned to the runtime's MYSQL_CODECS contract\n * (packages/runtime/src/codecs.ts) — see {@link MysqlPoolConfig}.\n */\n\nimport type {\n AdapterBudgetSupport,\n DatabaseAdapter,\n QueryResult,\n TransactionOptions,\n WireFidelity,\n} from \"@vibeorm/runtime\";\nimport type { Dialect, Provider } from \"@vibeorm/schema\";\nimport { internalError } from \"@vibeorm/schema\";\nimport { createPool as mysql2CreatePool } from \"mysql2/promise\";\nimport { MYSQL_DEFAULT_CONNECTION_LIMIT, acquireWithTimeout } from \"./acquire.ts\";\nimport { mapMysqlDriverError, runMapped } from \"./errors.ts\";\nimport { bindRowCountParams } from \"./placeholders.ts\";\nimport { normalizeDriverResult } from \"./results.ts\";\nimport {\n isTransactionDeadlineError,\n startTransactionBudget,\n type TransactionBudget,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nimport {\n createSavepointScope,\n runNestedSavepoint,\n runRootScope,\n scopeTurn,\n type SavepointScope,\n} from \"./savepoint-gate.ts\";\nimport {\n RESET_MAX_EXECUTION_TIME_SQL,\n classifyRawTransactionControl,\n maxExecutionTimeStatement,\n refuseNestedTransactionOptions,\n setIsolationLevelStatement,\n} from \"./transaction-sql.ts\";\n\n// ─── Minimal mysql2 surface ───────────────────────────────────────\n\n/** The parts of a mysql2 `PoolConnection` this adapter uses. */\nexport type MysqlConnectionLike = {\n /** Binary protocol — prepared statements, cached per connection by mysql2. */\n execute(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n /** Text protocol — raw path and transaction-control statements. */\n query(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n beginTransaction(): Promise<void>;\n commit(): Promise<void>;\n rollback(): Promise<void>;\n ping(): Promise<void>;\n release(): void;\n destroy(): void;\n};\n\n/** The parts of a mysql2 promise `Pool` this adapter uses. */\nexport type MysqlPoolLike = {\n execute(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n query(text: string, values?: unknown[]): Promise<[unknown, unknown]>;\n getConnection(): Promise<MysqlConnectionLike>;\n end(): Promise<void>;\n};\n\n/** Anything that can run a statement: the pool itself or a pinned connection. */\ntype Queryable = Pick<MysqlConnectionLike, \"execute\" | \"query\">;\n\n// ─── Pool config ──────────────────────────────────────────────────\n\n/**\n * The exact configuration this adapter hands to `mysql2.createPool`. The\n * literal-typed fields are pinned to the runtime's MYSQL_CODECS contract and\n * are not user-overridable:\n *\n * - `decimalNumbers: false` — DECIMAL comes back as a string; the Decimal\n * codec keeps it a string (no float rounding) and trims the fixed-scale\n * padding MySQL reports (`DECIMAL(65, 30)` → `\"42.420…00\"` → `\"42.42\"`).\n * - `supportBigNumbers: true` + `bigNumberStrings: true` — BIGINT (and\n * DECIMAL) always come back as strings, never a precision-lossy JS number;\n * the BigInt codec decodes strings exactly.\n * - `dateStrings: false` — DATETIME comes back as a JS `Date`; the DateTime\n * codec passes `Date` through in both directions.\n * - `jsonStrings: false` — JSON columns come back already parsed, which is\n * what the Json codec's decode expects from mysql2.\n * - `namedPlaceholders: false` — the mysql dialect renders positional `?`.\n * - `multipleStatements` defaults to false: the ORM never batches, and\n * @vibeorm/migrate's mysql runner applies DDL per-statement anyway (DDL\n * auto-commits, so per-statement progress tracking needs it).\n */\nexport type MysqlPoolConfig = {\n readonly uri?: string;\n readonly host?: string;\n readonly port?: number;\n readonly user?: string;\n readonly password?: string;\n readonly database?: string;\n readonly connectionLimit?: number;\n readonly charset?: string;\n readonly timezone: string;\n readonly decimalNumbers: false;\n readonly supportBigNumbers: true;\n readonly bigNumberStrings: true;\n readonly dateStrings: false;\n readonly jsonStrings: false;\n readonly namedPlaceholders: false;\n readonly multipleStatements: boolean;\n};\n\n// ─── Options ──────────────────────────────────────────────────────\n\n/**\n * Options for {@link mysqlAdapter}. Point it at a connection URL **or** at\n * discrete `host`/`port`/`user`/`password`/`database` fields (mysql2 accepts\n * both; when both are given the discrete fields win — mysql2 merges the parsed\n * URL underneath explicit config), or hand it a pool you already own.\n */\nexport type MysqlAdapterOptions = {\n /** MySQL connection URL (`mysql://user:pw@host:3306/db`). Defaults to `process.env.MYSQL_URL`. */\n readonly url?: string;\n readonly host?: string;\n readonly port?: number;\n readonly user?: string;\n readonly password?: string;\n readonly database?: string;\n /**\n * An existing mysql2 promise pool to use instead of creating one. The\n * adapter never ends a pool it did not create, so `disconnect()` is a no-op\n * in this mode. The pool MUST be configured to the codec contract\n * ({@link MysqlPoolConfig}) — the adapter cannot verify it.\n */\n readonly pool?: MysqlPoolLike;\n /** Maximum pooled connections (mysql2 default 10). Ignored when `pool` is given. */\n readonly connectionLimit?: number;\n /**\n * mysql2 `timezone`, default `\"Z\"`: the codec table stores DateTime as UTC\n * in `DATETIME(3)` columns, and DATETIME has no zone of its own — with\n * `\"Z\"` mysql2 serializes outgoing `Date`s as UTC and parses incoming\n * DATETIME text as UTC, so the instant round-trips regardless of the server\n * or process time zone. mysql2's own default (`\"local\"`) would silently\n * shift values by the process offset.\n */\n readonly timezone?: string;\n /** Connection charset/collation (mysql2 default `utf8mb4_general_ci`). */\n readonly charset?: string;\n /**\n * Milliseconds to wait for a pool connection before refusing with a\n * `VibeError` (`VIBE_ADAPTER`, `meta.reason: \"acquire-timeout\"`).\n *\n * **Unset (the default) keeps mysql2's own behaviour**: the pool is created\n * with `waitForConnections: true` and no time bound, so a caller whose\n * connections are all busy QUEUES until one frees. Setting this bounds EVERY\n * pool checkout the adapter makes — the caller gets a coded refusal instead\n * of an unbounded wait.\n *\n * Adapter-owned: mysql2 has no acquire timeout at all (its pool knobs are\n * `waitForConnections` and `queueLimit`, which bounds queue DEPTH, not time),\n * so this value is never passed to `mysql2.createPool`.\n *\n * **Not mysql2's `connectTimeout`**, which is the deadline for establishing a\n * NEW connection rather than for waiting on a busy pool. The equivalent\n * option on `@vibeorm/adapter-pg` is `connectionTimeout`; the name differs\n * here precisely because mysql2 already uses that word for something else.\n */\n readonly acquireTimeout?: number;\n /**\n * Allow multiple statements per `executeUnsafe` call (default false). Leave\n * it off: the ORM never batches, @vibeorm/migrate applies DDL per statement,\n * and enabling it widens the SQL-injection blast radius of raw queries.\n */\n readonly multipleStatements?: boolean;\n /**\n * Pool factory — a test seam. Defaults to `mysql2/promise`'s `createPool`;\n * tests inject a recording fake here to assert the exact config the adapter\n * builds. Ignored when `pool` is given.\n */\n readonly createPool?: (config: MysqlPoolConfig) => MysqlPoolLike;\n};\n\n// ─── Adapter ──────────────────────────────────────────────────────\n\nconst DIALECT: Dialect = \"mysql\";\nconst PROVIDER: Provider = \"mysql\";\n\n/**\n * mysql2 wire (board #30, live-verified on both execute paths, 8.4):\n * datetime(3) → `Date` (under the adapter's pinned `timezone: \"Z\"`), JSON →\n * parsed values, int/double → numbers. (BIGINT arrives as a string and\n * TINYINT(1) as 0/1 — BigInt/Boolean decoding stays.)\n */\nconst WIRE: WireFidelity = Object.freeze({ dateTime: \"native\", json: \"parsed\", numbers: \"native\" } as const);\n\n/**\n * Timing budgets this adapter genuinely enforces (EPIC 6).\n *\n * - `acquireTimeout: true` — {@link MysqlAdapterOptions.acquireTimeout} bounds\n * the checkout wait (mysql2 itself has none; ./acquire.ts explains why).\n * Declared as a capability; it is OFF until the option is set.\n * - `statementTimeout: \"select-only\"` — **MySQL's `MAX_EXECUTION_TIME` bounds\n * SELECT statements only.** INSERT/UPDATE/DELETE/DDL run unbounded, so this\n * is NOT general write cancellation and is not declared as one.\n * - `transactionDeadline: \"between-statements\"` — the budget is honoured before\n * each statement and at the commit. `\"cancel-running-statements\"` is REFUSED:\n * an in-flight write cannot be stopped through this connection, and killing\n * it from a second pooled connection would risk cancelling a session that had\n * already moved on.\n */\nconst BUDGETS: AdapterBudgetSupport = Object.freeze({\n acquireTimeout: true,\n statementTimeout: \"select-only\",\n transactionDeadline: \"between-statements\",\n} as const);\n\nfunction defaultCreatePool(config: MysqlPoolConfig): MysqlPoolLike {\n // Single cast seam: mysql2's Pool satisfies MysqlPoolLike structurally, but\n // its generic overloads don't reduce to our plain signatures.\n return mysql2CreatePool(config as Parameters<typeof mysql2CreatePool>[0]) as unknown as MysqlPoolLike;\n}\n\n/** Scalar-array params must never reach this adapter — the dialect expands IN. */\nfunction throwOnArrayParam(values: unknown[]): never {\n throw internalError({\n message:\n \"formatArrayParam called on the mysql adapter — the mysql dialect expands scalar-list parameters into IN (?, ?, …) placeholders, so the runtime should have expanded this array before binding\",\n meta: { length: values.length },\n });\n}\n\n/**\n * Create a VibeORM adapter backed by mysql2.\n *\n * @example\n * ```ts\n * const adapter = mysqlAdapter({ url: process.env.MYSQL_URL, connectionLimit: 20 });\n * await adapter.connect();\n * const rows = await adapter.execute({ text: \"SELECT * FROM `User` WHERE `id` = ?\", values: [1] });\n * ```\n *\n * @example Discrete fields, or reusing a pool you already own\n * ```ts\n * const adapter = mysqlAdapter({ host: \"127.0.0.1\", port: 3306, user: \"app\", password: \"pw\", database: \"app\" });\n * const shared = mysqlAdapter({ pool: existingPool });\n * ```\n *\n * ORM path (`execute`) uses mysql2's binary protocol (`pool.execute`), which\n * prepares statements and caches them per connection keyed by statement text\n * (mysql2's `maxPreparedStatements` LRU, default 16000). Raw path\n * (`executeUnsafe`) uses the text protocol (`pool.query`) — no statement\n * cache, and DDL (which the binary protocol cannot prepare) works.\n *\n * Parameters arrive PRE-ENCODED by the runtime codec table: Json is already a\n * string, DateTime is a `Date` (mysql2 serializes it per `timezone`), BigInt\n * is a string. The adapter forwards values verbatim — no second conversion.\n */\nexport function mysqlAdapter(options?: MysqlAdapterOptions): DatabaseAdapter {\n let pool: MysqlPoolLike | null = null;\n let ownsPool = false;\n\n /**\n * The pinned connection of an OPEN raw transaction — session affinity for\n * the raw path. A raw `BEGIN` … `COMMIT` sequence over `executeUnsafe` is\n * sound only when every statement in between rides the SAME pooled\n * connection, so a pure `BEGIN` checks one out, raw statements ride it, and\n * the matching `COMMIT`/`ROLLBACK` releases it (same semantics as the pg and\n * bun adapters). The ORM path (`execute`) and `transaction()` stay on the\n * pool. Concurrent raw transactions on one adapter are unsupported.\n */\n let rawSession: MysqlConnectionLike | null = null;\n\n const acquireTimeoutMs = options?.acquireTimeout;\n\n /**\n * Effective pool size for the refusal message. Unknown — and reported as\n * such — when the caller supplied the pool, since `connectionLimit` is\n * ignored in that mode.\n */\n const poolSize: number | undefined =\n options?.pool === undefined ? (options?.connectionLimit ?? MYSQL_DEFAULT_CONNECTION_LIMIT) : undefined;\n\n function getPool(): MysqlPoolLike {\n if (pool !== null) return pool;\n\n if (options?.pool !== undefined) {\n pool = options.pool;\n ownsPool = false;\n return pool;\n }\n\n const config: MysqlPoolConfig = {\n uri: options?.url ?? process.env.MYSQL_URL,\n host: options?.host,\n port: options?.port,\n user: options?.user,\n password: options?.password,\n database: options?.database,\n connectionLimit: options?.connectionLimit,\n charset: options?.charset,\n timezone: options?.timezone ?? \"Z\",\n decimalNumbers: false,\n supportBigNumbers: true,\n bigNumberStrings: true,\n dateStrings: false,\n jsonStrings: false,\n namedPlaceholders: false,\n multipleStatements: options?.multipleStatements ?? false,\n };\n pool = (options?.createPool ?? defaultCreatePool)(config);\n ownsPool = true;\n return pool;\n }\n\n /**\n * The ONE place a connection is checked out. Without `acquireTimeout` this is\n * mysql2's own unbounded checkout, byte for byte; with it, the wait is\n * bounded and a caller that cannot be served refuses instead of queueing.\n * `runMapped` maps driver failures — the timeout error is already a\n * `VibeError` and passes through untouched (errors.ts:121).\n */\n async function acquireConnection(): Promise<MysqlConnectionLike> {\n const active = getPool();\n if (acquireTimeoutMs === undefined) {\n return runMapped(() => active.getConnection());\n }\n return runMapped(() => acquireWithTimeout({ pool: active, timeoutMs: acquireTimeoutMs, poolSize }));\n }\n\n async function runExecute(params: {\n queryable: Queryable;\n text: string;\n values: unknown[];\n }): Promise<Record<string, unknown>[]> {\n const [result] = await runMapped(() => params.queryable.execute(params.text, params.values));\n return normalizeDriverResult({ result }).rows;\n }\n\n /**\n * The ORM's affected-row path (F01). Same PREPARED protocol as `execute()` —\n * mysql2 sends the statement and its parameters separately — but keeping the\n * OK-packet's `affectedRows` instead of dropping it.\n *\n * Never `query()`: that is the TEXT protocol, which interpolates values into\n * the SQL client-side. mysql2 cannot know the server's `sql_mode`, so under\n * `NO_BACKSLASH_ESCAPES` its `\\'` is not an escape and a hostile string\n * predicate breaks out of its literal — a `deleteMany` deleted rows a bound\n * read did not match. There is deliberately NO fallback from a failed\n * preparation to interpolation: an unpreparable statement fails loudly.\n *\n * RAW STATEMENTS RIDE THIS PATH TOO, so `bindRowCountParams` first rewrites\n * integers sitting in a `LIMIT` / `OFFSET` position into their decimal text:\n * mysql2's prepared protocol types a JS number in a way the server refuses\n * there (errno 1210), and only user-written raw SQL can produce that shape —\n * the ORM inlines its limits as literal text. Nothing else is touched.\n */\n async function runExecuteAffected(params: {\n queryable: Queryable;\n text: string;\n values: unknown[];\n }): Promise<QueryResult> {\n const values = bindRowCountParams({ text: params.text, values: params.values });\n const [result] = await runMapped(() => params.queryable.execute(params.text, values));\n return normalizeDriverResult({ result });\n }\n\n async function runExecuteUnsafe(params: {\n queryable: Queryable;\n text: string;\n values?: unknown[];\n }): Promise<QueryResult> {\n const [result] = await runMapped(() => params.queryable.query(params.text, params.values));\n return normalizeDriverResult({ result });\n }\n\n /**\n * Adapter bound to the ONE pinned connection of an open transaction — the\n * same-connection guarantee. Nested `transaction()` calls open savepoints on\n * that connection, and their LIFETIMES are serialized by the savepoint gate\n * (F14): overlapping siblings share one savepoint stack, so the earlier\n * one's rollback would erase the later one's already released work.\n */\n function createConnectionAdapter(params: {\n conn: MysqlConnectionLike;\n scope: SavepointScope;\n budget: TransactionBudget;\n }): DatabaseAdapter {\n const { conn, scope, budget } = params;\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n // After the turn, not before: waiting for a sibling savepoint spends\n // the same budget the statement is about to run against.\n budget.assertUsable({ stage: \"statement\" });\n return runExecute({ queryable: conn, ...execParams });\n },\n\n async executeUnsafe(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n budget.assertUsable({ stage: \"statement\" });\n return runExecuteUnsafe({ queryable: conn, ...execParams });\n },\n\n async executeAffected(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n budget.assertUsable({ stage: \"statement\" });\n return runExecuteAffected({ queryable: conn, ...execParams });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n nestedOptions?: TransactionOptions,\n ): Promise<T> {\n refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });\n budget.assertUsable({ stage: \"nested\" });\n return runNestedSavepoint({\n scope,\n execute: (statement) => runMapped(() => conn.query(statement)),\n // The child shares the ROOT budget by reference: a savepoint never\n // gets an independent clock or session setting.\n body: (child) => fn(createConnectionAdapter({ conn, scope: child, budget })),\n });\n },\n\n async connect(): Promise<void> {\n // Already connected — this adapter is bound to a pinned connection.\n },\n\n async disconnect(): Promise<void> {\n // The pool owns the connection lifecycle; releasing happens at the\n // top-level transaction boundary.\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return throwOnArrayParam(values);\n },\n };\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(params) {\n // Default path: mysql2's `pool.execute` does its own implicit checkout.\n // With `acquireTimeout` set that checkout has to be visible to be\n // bounded, so the statement is routed over an explicit connection\n // instead (mysql2 caches prepared statements per connection either way).\n if (acquireTimeoutMs === undefined) {\n return runExecute({ queryable: getPool(), ...params });\n }\n const conn = await acquireConnection();\n try {\n return await runExecute({ queryable: conn, ...params });\n } finally {\n conn.release();\n }\n },\n\n async executeAffected(params) {\n // Same checkout rules as `execute()` above: implicit with mysql2's own\n // pool, explicit (and therefore boundable) when `acquireTimeout` is set.\n if (acquireTimeoutMs === undefined) {\n return runExecuteAffected({ queryable: getPool(), ...params });\n }\n const conn = await acquireConnection();\n try {\n return await runExecuteAffected({ queryable: conn, ...params });\n } finally {\n conn.release();\n }\n },\n\n async executeUnsafe(params) {\n const control = classifyRawTransactionControl({ text: params.text });\n if (rawSession !== null) {\n if (control === \"close\") {\n const session = rawSession;\n rawSession = null;\n try {\n return await runExecuteUnsafe({ queryable: session, ...params });\n } finally {\n session.release();\n }\n }\n return runExecuteUnsafe({ queryable: rawSession, ...params });\n }\n if (control === \"open\") {\n const conn = await acquireConnection();\n try {\n const result = await runExecuteUnsafe({ queryable: conn, ...params });\n rawSession = conn;\n return result;\n } catch (error) {\n conn.release();\n throw error;\n }\n }\n // Default path: mysql2's `pool.query` checks a connection out\n // implicitly. Same reroute as `execute()` above, for the same reason —\n // a checkout has to be visible to be bounded, so with `acquireTimeout`\n // set the raw statement runs on an explicit connection too.\n if (acquireTimeoutMs === undefined) {\n return runExecuteUnsafe({ queryable: getPool(), ...params });\n }\n const conn = await acquireConnection();\n try {\n return await runExecuteUnsafe({ queryable: conn, ...params });\n } finally {\n conn.release();\n }\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n // Validate the timeout before taking a connection, so a bad option fails\n // fast instead of wasting a checkout.\n const timeoutText =\n transactionOptions?.timeout === undefined\n ? undefined\n : maxExecutionTimeStatement({ timeout: transactionOptions.timeout });\n // Same rule for the deadline: an unsupportable budget is refused before a\n // connection is taken, so it never costs a checkout.\n validateTransactionDeadline({\n options: transactionOptions,\n support: BUDGETS.transactionDeadline,\n provider: PROVIDER,\n });\n\n const conn = await acquireConnection();\n /** Set when the session state can no longer be asserted (see below). */\n let rollbackFailed = false;\n // The deadline clock starts with the connection already in hand:\n // acquisition has its own budget (`acquireTimeout`) and is not charged here.\n const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });\n try {\n // Session-scoped SELECT bound — see transaction-sql.ts for the loud\n // \"SELECT statements only\" caveat. Reset in finally below.\n if (timeoutText !== undefined) {\n await runMapped(() => conn.query(timeoutText));\n }\n // Applies to the NEXT transaction in the session → must precede begin.\n const isolationLevel = transactionOptions?.isolationLevel;\n if (isolationLevel !== undefined) {\n const isolationText = setIsolationLevelStatement({ isolationLevel });\n await runMapped(() => conn.query(isolationText));\n }\n await runMapped(() => conn.beginTransaction());\n const scope = createSavepointScope();\n try {\n const result = await runRootScope({\n scope,\n body: () => fn(createConnectionAdapter({ conn, scope, budget })),\n });\n // An expired budget must never commit, however the callback ended.\n budget.assertUsable({ stage: \"commit\" });\n await runMapped(() => conn.commit());\n budget.close({ outcome: \"committed\" });\n return result;\n } catch (error) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await conn.rollback();\n budget.close({ outcome: \"rolled-back\" });\n } catch {\n // The rollback that should have cleaned up failed: the commit state\n // is genuinely unknown and the connection is destroyed below rather\n // than pooled while it may still be inside a transaction.\n rollbackFailed = true;\n budget.close({ outcome: \"unknown\" });\n if (budget.totalMs !== null && isTransactionDeadlineError(error)) {\n throw transactionOutcomeUnknownError({\n provider: PROVIDER,\n totalMs: budget.totalMs,\n cause: error,\n });\n }\n }\n throw error;\n }\n } finally {\n let poisoned = rollbackFailed;\n if (timeoutText !== undefined && !poisoned) {\n try {\n await conn.query(RESET_MAX_EXECUTION_TIME_SQL);\n } catch {\n poisoned = true;\n }\n }\n // A connection whose session default could not be restored — or whose\n // rollback failed, so it may still be inside a transaction — must never\n // return to the pool.\n if (poisoned) {\n conn.destroy();\n } else {\n conn.release();\n }\n }\n },\n\n async connect(): Promise<void> {\n const conn = await acquireConnection();\n try {\n await runMapped(() => conn.ping());\n } finally {\n conn.release();\n }\n },\n\n async disconnect(): Promise<void> {\n if (rawSession !== null) {\n // An abandoned raw transaction must not leak an in-transaction\n // connection; roll it back best-effort before the pool ends.\n const session = rawSession;\n rawSession = null;\n try {\n await session.query(\"ROLLBACK\");\n } catch {\n /* the pool is closing; the session is going away regardless */\n }\n session.release();\n }\n if (pool !== null && ownsPool) {\n const closing = pool;\n pool = null;\n await runMapped(() => closing.end());\n }\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return throwOnArrayParam(values);\n },\n };\n}\n\n// ─── Re-exports (driver-error table, helpers) ─────────────────────\n\nexport {\n DEFAULT_ACQUIRE_TIMERS,\n MYSQL_DEFAULT_CONNECTION_LIMIT,\n acquireTimeoutError,\n acquireWithTimeout,\n} from \"./acquire.ts\";\nexport type { AcquireSource, AcquireTimers, ReleasableConnection, TimerHandle } from \"./acquire.ts\";\nexport {\n MYSQL_ERRNO_ERROR_CODES,\n MYSQL_ERRNO_REASONS,\n mapMysqlDriverError,\n mysqlErrorErrno,\n runMapped,\n} from \"./errors.ts\";\nexport { isResultSetHeader, normalizeDriverResult } from \"./results.ts\";\nexport { createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\nexport type { SavepointCounter } from \"./savepoints.ts\";\nexport {\n ISOLATION_LEVEL_SQL,\n RESET_MAX_EXECUTION_TIME_SQL,\n classifyRawTransactionControl,\n maxExecutionTimeStatement,\n refuseNestedTransactionOptions,\n setIsolationLevelStatement,\n} from \"./transaction-sql.ts\";\nexport type { RawTransactionControl } from \"./transaction-sql.ts\";\n// ─── EPIC 6: transaction budgets ──────────────────────────────────\nexport {\n DEFAULT_DEADLINE_CLOCK,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n transactionClosedError,\n transactionDeadlineError,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nexport type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from \"./transaction-budget.ts\";\n",
|
|
6
6
|
"/**\n * Bounded pool acquisition — the adapter's own acquire timeout.\n *\n * mysql2 has NO acquire timeout of its own (verified against mysql2 3.23.3:\n * neither `acquireTimeout` nor `acquire_timeout` exists anywhere in its\n * sources). Its pool backpressure knobs are `waitForConnections`\n * (`node_modules/mysql2/lib/pool_config.js:11-14`, default `true`) and\n * `queueLimit` (`:24-26`, default `0` = unlimited), which bounds queue DEPTH,\n * not TIME; `connectTimeout` is the TCP deadline for establishing a NEW\n * connection, not for waiting on a busy pool. So an exhausted pool queues the\n * caller forever unless something above the driver bounds the wait — which is\n * what this module does.\n *\n * The trap this module exists to get right: when the deadline wins the race,\n * the driver's `getConnection()` promise is STILL PENDING and will later hand\n * back a real connection nobody holds. Dropping it would turn \"waits too long\"\n * into \"the pool is dead forever\", so the losing promise is always released.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\n\n// ─── Seams ────────────────────────────────────────────────────────\n\n/** Timer handle, as the ambient `setTimeout` returns it. */\nexport type TimerHandle = ReturnType<typeof setTimeout>;\n\n/**\n * Timer seam — a test injects a counting pair to prove the deadline timer is\n * cleared exactly once, rather than asserting on wall-clock luck.\n */\nexport type AcquireTimers = {\n readonly set: (fn: () => void, ms: number) => TimerHandle;\n readonly clear: (handle: TimerHandle) => void;\n};\n\n/** The ambient timers, used whenever no seam is injected. */\nexport const DEFAULT_ACQUIRE_TIMERS: AcquireTimers = Object.freeze({\n set: (fn: () => void, ms: number): TimerHandle => setTimeout(fn, ms),\n clear: (handle: TimerHandle): void => clearTimeout(handle),\n});\n\n/** The one connection capability the late-release path needs. */\nexport type ReleasableConnection = {\n release(): void;\n};\n\n/** The one pool capability bounded acquisition needs. */\nexport type AcquireSource<TConnection extends ReleasableConnection> = {\n getConnection(): Promise<TConnection>;\n};\n\n// ─── Error ────────────────────────────────────────────────────────\n\n/** mysql2's own pool default when `connectionLimit` is unset (`lib/pool_config.js:15-17`). */\nexport const MYSQL_DEFAULT_CONNECTION_LIMIT: number = 10;\n\n/**\n * The refusal a bounded acquisition raises. `VIBE_ADAPTER` for parity with\n * `@vibeorm/adapter-pg`, whose `connectionTimeout` surfaces the same way, and\n * `meta.reason: \"acquire-timeout\"` so a caller can tell an exhausted pool from\n * any other adapter failure without reading message text.\n */\nexport function acquireTimeoutError(params: { timeoutMs: number; poolSize?: number }): VibeError {\n const { timeoutMs, poolSize } = params;\n const size =\n poolSize === undefined\n ? \"pool size unknown — the pool was supplied by the caller\"\n : `connectionLimit ${poolSize}`;\n return new VibeError({\n code: \"VIBE_ADAPTER\",\n message:\n `timed out after ${timeoutMs} ms waiting for a mysql pool connection — every pooled connection ` +\n `is busy (${size}). Raise \\`connectionLimit\\`, shorten the work holding a connection, or raise ` +\n \"the adapter's `acquireTimeout` option.\",\n meta: {\n reason: \"acquire-timeout\",\n timeoutMs,\n ...(poolSize === undefined ? {} : { connectionLimit: poolSize }),\n },\n });\n}\n\n// ─── Bounded acquisition ──────────────────────────────────────────\n\n/**\n * Check a connection out of `pool`, refusing with {@link acquireTimeoutError}\n * when it takes longer than `timeoutMs`.\n *\n * Two invariants make this safe:\n *\n * 1. **The lost acquire is released.** When the deadline wins, the pending\n * `getConnection()` is not abandoned: a handler released it as soon as it\n * lands, so a `connectionLimit: 1` pool keeps working after a timeout\n * instead of being permanently starved of its only connection.\n * 2. **The deadline timer never outlives the call.** It is cleared in a\n * `finally`, so a successful acquire leaves nothing pending that could keep\n * the event loop (or a test runner) alive.\n */\nexport async function acquireWithTimeout<TConnection extends ReleasableConnection>(params: {\n pool: AcquireSource<TConnection>;\n timeoutMs: number;\n /** Effective pool size, named in the refusal; omit when the pool is the caller's. */\n poolSize?: number;\n timers?: AcquireTimers;\n}): Promise<TConnection> {\n const { pool, timeoutMs, poolSize } = params;\n const timers = params.timers ?? DEFAULT_ACQUIRE_TIMERS;\n\n const pending = pool.getConnection();\n let timedOut = false;\n let handle: TimerHandle | undefined;\n\n const deadline = new Promise<never>((_resolve, reject) => {\n handle = timers.set(() => {\n timedOut = true;\n reject(acquireTimeoutError({ timeoutMs, poolSize }));\n }, timeoutMs);\n });\n\n // Trap: the loser of the race still settles. A connection that arrives after\n // the refusal belongs to nobody, so it goes straight back to the pool; a\n // rejection is already the race's business and is swallowed here so it can\n // never surface as an unhandled rejection.\n void pending\n .then(\n (connection) => {\n if (timedOut) connection.release();\n },\n () => undefined,\n )\n .catch(() => undefined);\n\n try {\n return await Promise.race([pending, deadline]);\n } finally {\n if (handle !== undefined) timers.clear(handle);\n }\n}\n",
|
|
7
7
|
"/**\n * Driver-error mapping for mysql2.\n *\n * Constitution rule 5: a raw driver error never escapes an adapter. Every\n * failure becomes a `VibeError` with a stable code, the driver error attached\n * as `cause`, and the MySQL errno / symbolic code / SQLSTATE / server message\n * in `meta`.\n *\n * mysql2 errors carry `errno` (the numeric MySQL server error, e.g. 1062),\n * `code` (the symbolic name, `ER_DUP_ENTRY`, or a client-side string like\n * `ECONNREFUSED` / `PROTOCOL_CONNECTION_LOST`), `sqlState` and `sqlMessage`.\n * The table is keyed by `errno` because it is the stable server-side identity;\n * client-side failures have no errno and fall through to `VIBE_ADAPTER`.\n */\n\nimport { VibeError, type VibeErrorCode } from \"@vibeorm/schema\";\n\n// ─── Errno table ──────────────────────────────────────────────────\n\n/**\n * MySQL server errno → stable VibeError code. Frozen and mirrored by tests;\n * anything absent from this table maps to `VIBE_ADAPTER` — including access\n * failures (1044 `ER_DBACCESS_DENIED_ERROR`, 1045 `ER_ACCESS_DENIED_ERROR`)\n * and every client-side connection error (`ECONNREFUSED`,\n * `PROTOCOL_CONNECTION_LOST`, `ETIMEDOUT`), which carry no errno at all.\n */\nexport const MYSQL_ERRNO_ERROR_CODES: Readonly<Record<number, VibeErrorCode>> = Object.freeze({\n /** ER_DUP_ENTRY */\n 1062: \"VIBE_UNIQUE_VIOLATION\",\n /** ER_DUP_ENTRY_WITH_KEY_NAME */\n 1586: \"VIBE_UNIQUE_VIOLATION\",\n /** ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO */\n 1761: \"VIBE_UNIQUE_VIOLATION\",\n /** ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO */\n 1762: \"VIBE_UNIQUE_VIOLATION\",\n /** ER_NO_REFERENCED_ROW */\n 1216: \"VIBE_FK_VIOLATION\",\n /** ER_ROW_IS_REFERENCED */\n 1217: \"VIBE_FK_VIOLATION\",\n /** ER_ROW_IS_REFERENCED_2 */\n 1451: \"VIBE_FK_VIOLATION\",\n /** ER_NO_REFERENCED_ROW_2 */\n 1452: \"VIBE_FK_VIOLATION\",\n /** ER_BAD_NULL_ERROR — NOT NULL column set to NULL */\n 1048: \"VIBE_VALIDATION\",\n /** ER_WARN_DATA_OUT_OF_RANGE — value out of column range */\n 1264: \"VIBE_VALIDATION\",\n /** ER_NO_DEFAULT_FOR_FIELD — column without default omitted */\n 1364: \"VIBE_VALIDATION\",\n /** ER_DATA_TOO_LONG */\n 1406: \"VIBE_VALIDATION\",\n /**\n * WARN_DATA_TRUNCATED — despite the WARN_ name, strict mode raises it as a\n * hard error: an out-of-range ENUM value or a truncated numeric string\n * (live-verified on 8.4: `role = \"SUPERUSER\"` and `n = \"12abc\"` → 1265).\n * The same bad input is a CHECK violation on sqlite and SQLSTATE 22P02 on\n * postgres — all VIBE_VALIDATION (board #9).\n */\n 1265: \"VIBE_VALIDATION\",\n /**\n * ER_TRUNCATED_WRONG_VALUE_FOR_FIELD — a wrong-TYPED value for the column\n * (live-verified on 8.4: `\"abc\"` into INT, `\"12.x9\"` into DECIMAL → 1366).\n */\n 1366: \"VIBE_VALIDATION\",\n /**\n * ER_TRUNCATED_WRONG_VALUE — an unparseable literal, e.g. a bad datetime\n * (live-verified on 8.4: `\"not-a-date\"` into DATETIME(3) → 1292,\n * SQLSTATE 22007).\n */\n 1292: \"VIBE_VALIDATION\",\n /** ER_CHECK_CONSTRAINT_VIOLATED */\n 3819: \"VIBE_VALIDATION\",\n /** ER_LOCK_DEADLOCK */\n 1213: \"VIBE_TRANSACTION\",\n /** ER_LOCK_WAIT_TIMEOUT */\n 1205: \"VIBE_TRANSACTION\",\n /** ER_QUERY_TIMEOUT — max_execution_time exceeded, statement interrupted */\n 3024: \"VIBE_TRANSACTION\",\n});\n\n/** Errnos that additionally explain themselves through `meta.reason`. */\nexport const MYSQL_ERRNO_REASONS: Readonly<Record<number, string>> = Object.freeze({\n /** ER_LOCK_DEADLOCK */\n 1213: \"deadlock\",\n /** ER_LOCK_WAIT_TIMEOUT */\n 1205: \"timeout\",\n /** ER_QUERY_TIMEOUT */\n 3024: \"timeout\",\n});\n\n// ─── Reading driver errors ────────────────────────────────────────\n\nfunction readStringField(params: { source: Record<string, unknown>; key: string }): string | undefined {\n const value = params.source[params.key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Extract the numeric MySQL errno from a mysql2 error. Client-side failures\n * (`ECONNREFUSED`, `PROTOCOL_CONNECTION_LOST`, …) have no numeric errno and\n * yield `undefined`.\n */\nexport function mysqlErrorErrno(params: { error: unknown }): number | undefined {\n const { error } = params;\n if (error === null || typeof error !== \"object\") return undefined;\n const errno = (error as Record<string, unknown>).errno;\n return typeof errno === \"number\" && Number.isInteger(errno) ? errno : undefined;\n}\n\n// ─── Mapping ──────────────────────────────────────────────────────\n\n/**\n * Map any error thrown by mysql2 onto a `VibeError`.\n *\n * A `VibeError` passes through unchanged, so an error raised deeper inside the\n * ORM (or by a nested adapter call) is never re-wrapped, and a user callback's\n * error is rethrown identically.\n */\nexport function mapMysqlDriverError(params: { error: unknown }): VibeError {\n const { error } = params;\n if (error instanceof VibeError) return error;\n\n const source: Record<string, unknown> =\n error !== null && typeof error === \"object\" ? (error as Record<string, unknown>) : {};\n const errno = mysqlErrorErrno({ error });\n const code: VibeErrorCode =\n (errno === undefined ? undefined : MYSQL_ERRNO_ERROR_CODES[errno]) ?? \"VIBE_ADAPTER\";\n\n const meta: Record<string, unknown> = {};\n if (errno !== undefined) meta.errno = errno;\n const symbolicCode = readStringField({ source, key: \"code\" });\n if (symbolicCode !== undefined) meta.code = symbolicCode;\n const sqlState = readStringField({ source, key: \"sqlState\" });\n if (sqlState !== undefined) meta.sqlState = sqlState;\n const sqlMessage = readStringField({ source, key: \"sqlMessage\" });\n if (sqlMessage !== undefined) meta.sqlMessage = sqlMessage.trim();\n const reason = errno === undefined ? undefined : MYSQL_ERRNO_REASONS[errno];\n if (reason !== undefined) meta.reason = reason;\n\n const message =\n readStringField({ source, key: \"message\" }) ??\n (sqlMessage !== undefined ? sqlMessage.trim() : undefined) ??\n (typeof error === \"string\" ? error : \"unknown mysql driver error\");\n\n return new VibeError({ code, message, meta, cause: error });\n}\n\n/** Run a driver call, mapping anything it throws onto a `VibeError`. */\nexport async function runMapped<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n throw mapMysqlDriverError({ error });\n }\n}\n",
|
|
8
|
+
"/**\n * Raw-path `?` placeholder analysis for mysql2's PREPARED protocol.\n *\n * WHY THIS EXISTS. F01 moved raw calls that carry parameters off mysql2's text\n * protocol (which interpolates values client-side and cannot know the server's\n * `sql_mode`) onto its prepared protocol. The prepared protocol types a JS\n * **number** in a way the server refuses in a ROW-COUNT position:\n *\n * execute(\"SELECT 1 LIMIT ?\", [2]) → errno 1210 ER_WRONG_ARGUMENTS\n * execute(\"SELECT 1 LIMIT ?\", [\"2\"]) → OK\n * execute(\"SELECT ? AS a\", [2]) → OK\n *\n * so the ONE narrow repair is: bind a row-count placeholder's integer as its\n * decimal TEXT, and leave every other binding byte-for-byte as it was.\n * Stringifying an integer at a `LIMIT`/`OFFSET` position is lossless — MySQL\n * parses it back to the same unsigned integer — while stringifying anywhere\n * else would change the wire type of ordinary comparisons.\n *\n * The ORM never emits `LIMIT ?` (the renderer inlines its limits as literal\n * text), so this only ever fires for user-written `$queryRaw` /\n * `$queryRawUnsafe` / `$executeRaw*`.\n *\n * SCANNING IS CONSERVATIVE. Finding row-count placeholders means finding the\n * REAL `?` tokens first, so the scanner skips string literals, backtick-quoted\n * identifiers and all three MySQL comment forms. Two self-checks keep a\n * mis-scan from ever mis-binding: the placeholder count must match the\n * parameter count, and only placeholders in a row-count position are touched.\n * When either check fails the values pass through EXACTLY as they do today.\n *\n * Known scanner limits, all of which fail into that pass-through:\n * - `NO_BACKSLASH_ESCAPES`: a backslash is treated as an escape here, so under\n * that mode a literal ending in `\\` can be mis-measured. The count check then\n * disagrees and nothing is transformed.\n * - `/*! … *\\/` executable comments are skipped like ordinary comments, so a\n * placeholder inside one is not counted — again a count mismatch.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\n\n/** Where the real `?` placeholders are, and which of them are row counts. */\nexport type ScannedPlaceholders = {\n /** How many real placeholders the statement has. */\n readonly count: number;\n /** 0-based ordinals of the placeholders governed by `LIMIT` / `OFFSET`. */\n readonly rowCountPositions: ReadonlySet<number>;\n};\n\n/** Cheap pre-filter: no row-count clause is possible without one of these words. */\nconst ROW_COUNT_HINT = /\\b(?:limit|offset)\\b/i;\n\nfunction isIdentChar(char: string | undefined): boolean {\n return char !== undefined && /[A-Za-z0-9_$]/.test(char);\n}\n\n/**\n * Index just past a quoted run starting at `start` — `'…'`, `\"…\"` or\n * `` `…` ``. A doubled quote continues the run (MySQL's own escape) and a\n * backslash escapes the next character (MySQL's default mode).\n */\nfunction skipQuoted(text: string, start: number, quote: string): number {\n let index = start + 1;\n while (index < text.length) {\n const char = text[index];\n if (char === \"\\\\\") {\n index += 2;\n continue;\n }\n if (char === quote) {\n if (text[index + 1] === quote) {\n index += 2;\n continue;\n }\n return index + 1;\n }\n index += 1;\n }\n return text.length; // unterminated — the driver reports it, not this scanner\n}\n\n/** Index just past a line comment (`-- ` or `#`). */\nfunction skipLineComment(text: string, start: number): number {\n const newline = text.indexOf(\"\\n\", start);\n return newline === -1 ? text.length : newline + 1;\n}\n\n/** Index just past a block comment. MySQL does not nest them. */\nfunction skipBlockComment(text: string, start: number): number {\n const close = text.indexOf(\"*/\", start + 2);\n return close === -1 ? text.length : close + 2;\n}\n\n/**\n * Scan a statement for its real `?` placeholders, marking the ones a `LIMIT` or\n * `OFFSET` keyword governs.\n *\n * The row-count context opens on the word `LIMIT` or `OFFSET` and survives only\n * whitespace, comments, commas, integer literals and further placeholders —\n * exactly the tokens MySQL's `LIMIT {[offset,] count | count OFFSET offset}`\n * grammar allows. Any other word or symbol closes it, so `?` anywhere else is\n * left alone.\n */\nexport function scanPlaceholders(params: { text: string }): ScannedPlaceholders {\n const { text } = params;\n const rowCountPositions = new Set<number>();\n let count = 0;\n let inRowCount = false;\n let index = 0;\n\n while (index < text.length) {\n const char = text[index] as string;\n\n if (char === \"'\" || char === '\"' || char === \"`\") {\n index = skipQuoted(text, index, char);\n inRowCount = false;\n continue;\n }\n // `--` is a comment only when whitespace follows it (MySQL, unlike others).\n if (char === \"-\" && text[index + 1] === \"-\" && (text[index + 2] === undefined || /\\s/.test(text[index + 2] as string))) {\n index = skipLineComment(text, index);\n continue;\n }\n if (char === \"#\") {\n index = skipLineComment(text, index);\n continue;\n }\n if (char === \"/\" && text[index + 1] === \"*\") {\n index = skipBlockComment(text, index);\n continue;\n }\n if (char === \"?\") {\n if (inRowCount) rowCountPositions.add(count);\n count += 1;\n index += 1;\n continue;\n }\n // Whitespace and commas carry the context across `LIMIT ?, ?`.\n if (/\\s/.test(char) || char === \",\") {\n index += 1;\n continue;\n }\n // An integer literal is a legal row count — the context survives it.\n if (/[0-9]/.test(char)) {\n while (index < text.length && /[0-9]/.test(text[index] as string)) index += 1;\n continue;\n }\n if (/[A-Za-z_$]/.test(char)) {\n const start = index;\n while (isIdentChar(text[index])) index += 1;\n const word = text.slice(start, index).toUpperCase();\n inRowCount = word === \"LIMIT\" || word === \"OFFSET\";\n continue;\n }\n inRowCount = false;\n index += 1;\n }\n\n return { count, rowCountPositions };\n}\n\n/**\n * A row count as mysql2's prepared protocol will accept it: the integer's\n * decimal text.\n *\n * A number that is not a non-negative safe integer is REFUSED with a typed\n * error rather than rounded or guessed — `LIMIT 1.5` has no meaning, and a\n * value past `Number.MAX_SAFE_INTEGER` has already lost digits before this\n * function can see it. A `bigint` of any size converts losslessly, so it is\n * accepted whenever it is non-negative. Every other type (strings, `null`,\n * `undefined`) passes through untouched: mysql2 already sends those verbatim,\n * and MySQL's own error names anything it dislikes.\n */\nfunction toRowCount(params: { value: unknown; ordinal: number; text: string }): unknown {\n const { value, ordinal, text } = params;\n const refuse = (reason: string): never => {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message:\n `parameter $${ordinal} sits in a LIMIT/OFFSET position and ${reason} — ` +\n \"a row count must be a non-negative integer\",\n meta: { placeholder: ordinal, value: String(value), text },\n });\n };\n if (typeof value === \"number\") {\n if (!Number.isSafeInteger(value)) return refuse(\"is not a safe integer\");\n if (value < 0) return refuse(\"is negative\");\n return String(value);\n }\n if (typeof value === \"bigint\") {\n if (value < 0n) return refuse(\"is negative\");\n return value.toString();\n }\n return value;\n}\n\n/**\n * The parameter list to hand mysql2's prepared protocol: identical to the one\n * passed in, except that integers in a `LIMIT` / `OFFSET` position become their\n * decimal text.\n *\n * Returns the SAME array when nothing needs changing, so the ORM's own\n * affected-row path — which never emits a row-count placeholder — pays only a\n * word test.\n */\nexport function bindRowCountParams(params: { text: string; values: unknown[] }): unknown[] {\n const { text, values } = params;\n if (values.length === 0) return values;\n if (!values.some((value) => typeof value === \"number\" || typeof value === \"bigint\")) return values;\n if (!ROW_COUNT_HINT.test(text)) return values;\n\n const scanned = scanPlaceholders({ text });\n // Self-check: an unparsed shape must never shift a binding.\n if (scanned.rowCountPositions.size === 0 || scanned.count !== values.length) return values;\n\n return values.map((value, index) =>\n scanned.rowCountPositions.has(index) ? toRowCount({ value, ordinal: index + 1, text }) : value,\n );\n}\n",
|
|
8
9
|
"/**\n * Result normalization for mysql2.\n *\n * mysql2 resolves `execute()` / `query()` with a `[result, fields]` tuple where\n * `result` is one of:\n * - `RowDataPacket[]` — a SELECT's rows (plain objects; `rowsAsArray` is off);\n * - `ResultSetHeader` — an INSERT / UPDATE / DELETE / DDL OK-packet carrying\n * `affectedRows` and `insertId`;\n * - an array of the above — only when `multipleStatements` is enabled, one\n * entry per statement.\n *\n * The adapter's contract: `execute` returns rows (a header normalizes to `[]`);\n * `executeUnsafe` returns `{ rows, affectedRows }` (a SELECT has\n * `affectedRows: 0`, a mutation has `rows: []`). With `multipleStatements`\n * enabled the LAST statement's result wins, mirroring adapter-pglite's `exec`.\n */\n\nimport type { QueryResult } from \"@vibeorm/runtime\";\n\n// ─── Header detection ─────────────────────────────────────────────\n\n/**\n * True when a driver result value is shaped like mysql2's `ResultSetHeader`\n * (an OK-packet). Checks `affectedRows` + `serverStatus` together so a row\n * object that merely aliases a column as `affectedRows` is not misread.\n */\nexport function isResultSetHeader(params: { value: unknown }): boolean {\n const { value } = params;\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n const source = value as Record<string, unknown>;\n return typeof source.affectedRows === \"number\" && typeof source.serverStatus === \"number\";\n}\n\n// ─── Normalization ────────────────────────────────────────────────\n\n/** One result set (never a multi-statement array) → the adapter contract. */\nfunction normalizeSingle(value: unknown): QueryResult {\n if (Array.isArray(value)) {\n return { rows: value as Record<string, unknown>[], affectedRows: 0 };\n }\n if (isResultSetHeader({ value })) {\n return { rows: [], affectedRows: (value as { affectedRows: number }).affectedRows };\n }\n return { rows: [], affectedRows: 0 };\n}\n\n/**\n * Normalize the first element of mysql2's `[result, fields]` tuple. A\n * multi-statement result (array whose last entry is itself a result set or an\n * OK-packet) collapses to its LAST statement's result.\n */\nexport function normalizeDriverResult(params: { result: unknown }): QueryResult {\n const { result } = params;\n if (Array.isArray(result)) {\n const last: unknown = result.at(-1);\n if (last !== undefined && (Array.isArray(last) || isResultSetHeader({ value: last }))) {\n return normalizeSingle(last);\n }\n // A plain result set: row objects (or an empty SELECT).\n return { rows: result as Record<string, unknown>[], affectedRows: 0 };\n }\n return normalizeSingle(result);\n}\n",
|
|
10
|
+
"/**\n * Transaction budgets — the total-deadline clock and the handle-closure gate.\n *\n * BYTE-IDENTICAL in all six adapter packages, exactly like `savepoint-gate.ts`:\n * adapters depend on `@vibeorm/runtime` for TYPES only, so a shared value\n * module there would invert the dependency direction, and `@vibeorm/schema` is\n * the IR package, not a home for a transaction clock. Keep the six copies in\n * lockstep — a divergence here is a silent per-engine behaviour difference.\n *\n * WHAT A DEADLINE IS, AND IS NOT\n *\n * `TransactionOptions.timeout` is unchanged: a PER-STATEMENT engine-side bound.\n * `TransactionOptions.deadline` is new and different — a TOTAL wall-clock bound\n * on one top-level transaction. Its clock starts immediately before `BEGIN` is\n * sent, so connection acquisition (which has its own budget on the pooled\n * adapters) is deliberately NOT counted.\n *\n * Enforcement is never `Promise.race` over a statement that keeps running:\n *\n * - `\"between-statements\"` (the default, and all six adapters can deliver it):\n * before every statement, every nested `transaction()` and the `COMMIT`, an\n * expired budget refuses. The statement is never sent and the transaction is\n * rolled back. A statement ALREADY IN FLIGHT is not interrupted.\n * - `\"cancel-running-statements\"` (postgres servers only): additionally hands\n * the engine the remaining budget as `statement_timeout`, so the SERVER\n * cancels an over-long statement. An adapter that cannot do this refuses the\n * request outright rather than quietly delivering the weaker level.\n *\n * JavaScript cannot forcibly stop a running callback. A callback that keeps\n * going past the deadline finds its later database calls refused; work it does\n * outside the database (an HTTP call, an SMS) is not stopped and cannot be.\n * There is no automatic retry: a deadline failure is terminal for that\n * transaction, because external side effects may already have happened.\n */\n\nimport type {\n TransactionDeadlineEnforcement,\n TransactionDeadlineSupport,\n TransactionOptions,\n} from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n// ─── Types ────────────────────────────────────────────────────────\n\n/** Where a budget check happened, reported as `meta.stage`. */\nexport type BudgetStage = \"statement\" | \"nested\" | \"commit\";\n\n/**\n * What is known about a finished transaction. `\"unknown\"` is a first-class\n * outcome, not a failure to decide: when a rollback itself fails the commit\n * state genuinely cannot be asserted, and claiming \"rolled back\" would be a lie.\n */\nexport type TransactionOutcome = \"committed\" | \"rolled-back\" | \"unknown\";\n\n/** Wall clock, injectable so tests are deterministic instead of sleep-timed. */\nexport type DeadlineClock = () => number;\n\n/** The ambient clock, used whenever no seam is injected. */\nexport const DEFAULT_DEADLINE_CLOCK: DeadlineClock = (): number => Date.now();\n\n/**\n * One top-level transaction's budget. Nested savepoint handles share this\n * object BY REFERENCE — they never get an independent clock, connection or\n * session setting.\n */\nexport type TransactionBudget = {\n /** The enforcement level in force, or `null` when no deadline was requested. */\n readonly enforcement: TransactionDeadlineEnforcement | null;\n /** The requested total budget in milliseconds, or `null` when none was. */\n readonly totalMs: number | null;\n /** Existing per-statement limit, independent of the total deadline. Zero disables it. */\n readonly statementTimeoutMs: number | null;\n /** Milliseconds left (never negative), or `null` when no deadline is set. */\n remainingMs(): number | null;\n /** True only when a deadline is set and has passed. */\n expired(): boolean;\n /** Refuse a closed handle or an expired budget. Called BEFORE anything is sent. */\n assertUsable(params: { stage: BudgetStage }): void;\n /** Record the terminal outcome; the first call wins and later ones are ignored. */\n close(params: { outcome: TransactionOutcome }): void;\n /** The recorded outcome, or `null` while the transaction is still open. */\n readonly outcome: TransactionOutcome | null;\n};\n\n// ─── Validation (before BEGIN, before any write) ──────────────────\n\n/**\n * Check `options.deadline` against what this adapter can honestly deliver.\n * Called BEFORE `BEGIN` — a budget this engine cannot honour must never leave\n * a transaction open behind it.\n *\n * @throws VibeError `VIBE_VALIDATION` when `totalMs` is not a positive integer.\n * @throws VibeError `VIBE_UNSUPPORTED_CAPABILITY` when the requested\n * `enforcement` is stronger than `support` — refused, never silently degraded.\n */\nexport function validateTransactionDeadline(params: {\n options?: TransactionOptions;\n support: TransactionDeadlineSupport;\n provider: string;\n}): void {\n const { options, support, provider } = params;\n const deadline = options?.deadline;\n if (deadline === undefined) return;\n\n if (support === \"unsupported\") {\n throw new VibeError({\n code: \"VIBE_UNSUPPORTED_CAPABILITY\",\n message: `${provider} cannot enforce a transaction deadline — remove the \\`deadline\\` option`,\n meta: { provider, feature: \"transactionDeadline\", supported: support },\n });\n }\n\n if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction deadline \\`totalMs\\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,\n meta: { provider, feature: \"transactionDeadline\", totalMs: deadline.totalMs },\n });\n }\n\n const requested: TransactionDeadlineEnforcement = deadline.enforcement ?? \"between-statements\";\n if (requested === \"cancel-running-statements\" && support !== \"cancel-running-statements\") {\n throw new VibeError({\n code: \"VIBE_UNSUPPORTED_CAPABILITY\",\n message:\n `${provider} cannot cancel a statement that is already running, so the requested ` +\n \"`cancel-running-statements` deadline guarantee is refused rather than silently downgraded — \" +\n 'ask for \"between-statements\" if a budget checked between statements is enough',\n meta: { provider, feature: \"transactionDeadline\", requested, supported: support },\n });\n }\n}\n\n// ─── The budget ───────────────────────────────────────────────────\n\n/**\n * Start the clock for one top-level transaction. Call it immediately before\n * `BEGIN`; {@link validateTransactionDeadline} must already have run.\n */\nexport function startTransactionBudget(params: {\n options?: TransactionOptions;\n provider: string;\n clock?: DeadlineClock;\n}): TransactionBudget {\n const { options, provider } = params;\n const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;\n const deadline = options?.deadline;\n const totalMs = deadline === undefined ? null : deadline.totalMs;\n const enforcement: TransactionDeadlineEnforcement | null =\n deadline === undefined ? null : (deadline.enforcement ?? \"between-statements\");\n const expiresAt = totalMs === null ? null : clock() + totalMs;\n let outcome: TransactionOutcome | null = null;\n\n const budget: TransactionBudget = {\n enforcement,\n totalMs,\n statementTimeoutMs: options?.timeout ?? null,\n remainingMs(): number | null {\n return expiresAt === null ? null : Math.max(0, expiresAt - clock());\n },\n expired(): boolean {\n return expiresAt !== null && clock() >= expiresAt;\n },\n assertUsable(assertParams: { stage: BudgetStage }): void {\n if (outcome !== null) {\n throw transactionClosedError({ provider, outcome, stage: assertParams.stage });\n }\n if (expiresAt !== null && clock() >= expiresAt) {\n throw transactionDeadlineError({\n provider,\n stage: assertParams.stage,\n totalMs: totalMs as number,\n overdueMs: clock() - expiresAt,\n });\n }\n },\n close(closeParams: { outcome: TransactionOutcome }): void {\n if (outcome === null) outcome = closeParams.outcome;\n },\n get outcome(): TransactionOutcome | null {\n return outcome;\n },\n };\n return budget;\n}\n\n/**\n * The engine-side bound for the NEXT statement, or `null` when the caller did\n * not buy `\"cancel-running-statements\"`. Floored at 1 ms: on postgres `0` means\n * \"no limit\", so a spent budget must never be handed over as a zero.\n */\nexport function engineStatementBudgetMs(params: { budget: TransactionBudget }): number | null {\n const { budget } = params;\n if (budget.enforcement !== \"cancel-running-statements\") return null;\n const remaining = budget.remainingMs();\n if (remaining === null) return null;\n const totalBound = Math.max(1, Math.ceil(remaining));\n return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0\n ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;\n}\n\n// ─── Errors ───────────────────────────────────────────────────────\n\n/**\n * The refusal an expired budget raises. `VIBE_TRANSACTION` with a stable\n * `meta.reason` — no SQL text, no parameter values, no credentials.\n */\nexport function transactionDeadlineError(params: {\n provider: string;\n stage: BudgetStage;\n totalMs: number;\n overdueMs: number;\n}): VibeError {\n const { provider, stage, totalMs, overdueMs } = params;\n const what =\n stage === \"commit\"\n ? \"the transaction was rolled back instead of committed\"\n : \"the statement was never sent and the transaction is being rolled back\";\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `transaction deadline of ${totalMs} ms expired ${Math.max(0, Math.round(overdueMs))} ms ago — ${what}. ` +\n \"Nothing is retried automatically: work the callback did outside the database may already have happened.\",\n meta: { provider, reason: \"transaction-deadline\", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) },\n });\n}\n\n/**\n * The refusal an ESCAPED transaction handle raises — a transactional adapter\n * kept past the end of its transaction. Without this it would run on a\n * connection that is back in the pool, outside any transaction.\n */\nexport function transactionClosedError(params: {\n provider: string;\n outcome: TransactionOutcome;\n stage: BudgetStage;\n}): VibeError {\n const { provider, outcome, stage } = params;\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `this transaction has already finished (${outcome}), so its client can no longer be used — ` +\n \"a handle kept past the end of `$transaction` would run outside the transaction, on a connection \" +\n \"that now belongs to someone else\",\n meta: { provider, reason: \"transaction-closed\", outcome, stage },\n });\n}\n\n/**\n * The stable typed outcome for \"the deadline expired and the rollback that was\n * supposed to clean up failed too\". Promising a rollback here would be false;\n * promising a retry would be worse.\n *\n * `cause` is the CAUSAL error (the deadline refusal), so the reason the\n * transaction was being abandoned survives. The rollback failure itself is\n * reported only as `meta.rollbackFailed`: a raw driver failure can carry\n * statement text, and this error is meant to be logged.\n */\nexport function transactionOutcomeUnknownError(params: {\n provider: string;\n totalMs: number;\n cause: unknown;\n}): VibeError {\n const { provider, totalMs, cause } = params;\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `transaction deadline of ${totalMs} ms expired and the rollback that should have cleaned up failed — ` +\n \"the commit state of this transaction is UNKNOWN. It is not retried automatically; inspect the data \" +\n \"before deciding.\",\n meta: {\n provider,\n reason: \"transaction-outcome-unknown\",\n outcome: \"unknown\",\n totalMs,\n rollbackFailed: true,\n },\n cause,\n });\n}\n\n/** Whether `error` is this module's deadline refusal (used to pick the cleanup path). */\nexport function isTransactionDeadlineError(error: unknown): boolean {\n const reason = (error as { meta?: { reason?: unknown } } | null)?.meta?.reason;\n return reason === \"transaction-deadline\";\n}\n",
|
|
11
|
+
"/**\n * Savepoint lifetimes inside one top-level transaction (F14).\n *\n * A savepoint stack lives on ONE connection: `ROLLBACK TO SAVEPOINT s` undoes\n * everything done after `s`, including work a LATER savepoint already released.\n * Two `transaction()` callbacks opened from the same handle and overlapping in\n * time therefore share that one stack, and the earlier one's rollback silently\n * erases the later one's already committed work. Unique savepoint names do not\n * help — the names never collided, the lifetimes did.\n *\n * The rule enforced here, on every handle of a transaction:\n *\n * > A handle runs when nothing deeper than itself is open. Otherwise it waits\n * > for the innermost open savepoint to settle — unless the call is being made\n * > from INSIDE that savepoint's own callback, which is genuine nesting and\n * > proceeds immediately.\n *\n * The \"from inside\" test is an {@link AsyncLocalStorage} lookup, so the common\n * nesting shapes never wait on themselves: a child opened from its parent's\n * callback, and the parent handle used from within an open child's callback,\n * both proceed. Only genuinely concurrent siblings serialize.\n *\n * Not covered: a callback that awaits a sibling it did not start. That\n * arrangement asks for two savepoints to be open at once, which is exactly what\n * loses work; it now blocks instead. Deliberate — see the notes for this fix.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { type SavepointCounter, createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\n\n// ─── Types ────────────────────────────────────────────────────────\n\n/**\n * One handle's position in the savepoint stack of a top-level transaction.\n * `state` is shared by reference across every handle of that transaction.\n */\nexport type SavepointScope = {\n /** Physical savepoint depth; 0 is the top-level transaction itself. */\n readonly depth: number;\n /** Shared state: name allocation plus the innermost open savepoint. */\n readonly state: SavepointState;\n};\n\n/** Mutable state shared by every scope of one top-level transaction. */\nexport type SavepointState = {\n /** Savepoint name counter (shared, so names never collide). */\n readonly counter: SavepointCounter;\n /** The innermost savepoint currently open, or `null` when none is. */\n open: OpenSavepoint | null;\n};\n\n/** An open savepoint: its handle, its name, and when it finished. */\ntype OpenSavepoint = {\n readonly scope: SavepointScope;\n readonly name: string;\n readonly parent: OpenSavepoint | null;\n readonly settled: Promise<void>;\n readonly settle: () => void;\n};\n\n/**\n * The savepoint whose callback the current async context is running inside.\n * This is the discriminator between genuine nesting (proceed) and an\n * overlapping sibling (wait).\n */\nconst CURRENT = new AsyncLocalStorage<SavepointScope>();\n\n// ─── Public API ───────────────────────────────────────────────────\n\n/** Fresh scope for one top-level transaction: depth 0, nothing open. */\nexport function createSavepointScope(): SavepointScope {\n return { depth: 0, state: { counter: createSavepointCounter(), open: null } };\n}\n\n/**\n * Run a top-level transaction callback inside its scope's async context, so\n * nested calls made from it can be told apart from calls made inside an open\n * child.\n */\nexport function runRootScope<T>(params: { scope: SavepointScope; body: () => Promise<T> }): Promise<T> {\n return CURRENT.run(params.scope, params.body);\n}\n\n/**\n * The wait a statement on `scope` owes before it may run, or `undefined` when\n * it may run now. Returning `undefined` keeps the uncontended path — nothing\n * deeper open — free of an extra promise and microtask.\n */\nexport function scopeTurn(params: { scope: SavepointScope }): Promise<void> | undefined {\n const open = params.scope.state.open;\n if (mayProceed({ scope: params.scope, open })) return undefined;\n return waitForTurn({ scope: params.scope });\n}\n\n/**\n * Open a nested savepoint under `scope`, run `body` inside it and release it —\n * rolling back to it if `body` throws, exactly as before this gate existed.\n *\n * `execute` runs the savepoint control SQL (already wrapped in the adapter's\n * own driver-error mapping). Omit it when the driver manages the savepoint\n * itself (bun:sql's `savepoint()`), in which case only the lifetime is gated.\n */\nexport async function runNestedSavepoint<T>(params: {\n readonly scope: SavepointScope;\n readonly execute?: (statement: string) => Promise<unknown> | unknown;\n readonly body: (scope: SavepointScope) => Promise<T>;\n}): Promise<T> {\n const { scope, execute, body } = params;\n const node = await acquire({ scope });\n try {\n if (execute !== undefined) await execute(`SAVEPOINT ${node.name}`);\n let result: T;\n try {\n result = await CURRENT.run(node.scope, () => body(node.scope));\n } catch (error) {\n // A descendant the callback started but never awaited must finish before\n // this savepoint may be rolled back — releasing out of order corrupts\n // the stack.\n await drainDescendants({ node });\n if (execute !== undefined) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);\n } catch {\n /* rollback is best-effort */\n }\n }\n throw error;\n }\n await drainDescendants({ node });\n if (execute !== undefined) await execute(`RELEASE SAVEPOINT ${node.name}`);\n return result;\n } finally {\n closeNode({ node });\n }\n}\n\n// ─── Internals ────────────────────────────────────────────────────\n\n/** Whether `scope` may act while `open` is the innermost open savepoint. */\nfunction mayProceed(params: { scope: SavepointScope; open: OpenSavepoint | null }): boolean {\n const { scope, open } = params;\n if (open === null) return true;\n // This handle IS the innermost open savepoint.\n if (open.scope === scope) return true;\n // This handle's own savepoint has already closed (a retained child handle):\n // it can only act at the connection's current depth, and waiting for a\n // shallower savepoint it does not own would never resolve.\n if (open.scope.depth < scope.depth) return true;\n // Genuine nesting: the call is being made from inside the open callback.\n return CURRENT.getStore() === open.scope;\n}\n\nasync function waitForTurn(params: { scope: SavepointScope }): Promise<void> {\n for (;;) {\n const open = params.scope.state.open;\n if (open === null || mayProceed({ scope: params.scope, open })) return;\n await open.settled;\n }\n}\n\n/**\n * Wait for this handle's turn and take it in the SAME synchronous step — the\n * check and the push must not be separated by an await, or two waiters woken\n * together would both open a savepoint.\n */\nasync function acquire(params: { scope: SavepointScope }): Promise<OpenSavepoint> {\n const { scope } = params;\n for (;;) {\n const open = scope.state.open;\n if (mayProceed({ scope, open })) return pushNode({ scope, parent: open });\n // `mayProceed` returns true for a null `open`, so this is never null here.\n await (open as OpenSavepoint).settled;\n }\n}\n\nfunction pushNode(params: { scope: SavepointScope; parent: OpenSavepoint | null }): OpenSavepoint {\n const { scope, parent } = params;\n const state = scope.state;\n let settle: () => void = () => undefined;\n const settled = new Promise<void>((resolve) => {\n settle = () => resolve();\n });\n // Physical depth follows the connection's stack, not the handle that opened\n // it: a savepoint opened from an outer handle inside a child's callback still\n // nests inside that child.\n const node: OpenSavepoint = {\n scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },\n name: nextSavepointName({ counter: state.counter }),\n parent,\n settled,\n settle,\n };\n state.open = node;\n return node;\n}\n\nfunction closeNode(params: { node: OpenSavepoint }): void {\n const { node } = params;\n const state = node.scope.state;\n if (state.open === node) state.open = node.parent;\n node.settle();\n}\n\n/** Wait out savepoints a callback opened below this node and never awaited. */\nasync function drainDescendants(params: { node: OpenSavepoint }): Promise<void> {\n const state = params.node.scope.state;\n for (;;) {\n const open = state.open;\n if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth) return;\n await open.settled;\n }\n}\n",
|
|
9
12
|
"/**\n * Savepoint naming for nested transactions.\n *\n * v1 lesson (LEARNINGS.md): the counter is shared **by reference** across every\n * adapter created for one top-level transaction, so sibling and deeply nested\n * savepoints can never collide on a name.\n */\n\n/** Mutable counter shared by reference across one top-level transaction. */\nexport type SavepointCounter = { n: number };\n\n/** Fresh counter — one per top-level transaction. */\nexport function createSavepointCounter(): SavepointCounter {\n return { n: 0 };\n}\n\n/**\n * Allocate the next savepoint name (`vibeorm_sp_0`, `vibeorm_sp_1`, …) and\n * advance the shared counter. The name is an identifier by construction, so it\n * is safe to interpolate into `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` SQL.\n */\nexport function nextSavepointName(params: { counter: SavepointCounter }): string {\n return `vibeorm_sp_${params.counter.n++}`;\n}\n",
|
|
10
|
-
"/**\n * Transaction-control SQL text (mysql dialect).\n *\n * Kept apart from the adapter so the mapping from `TransactionOptions` to SQL\n * is unit-testable without a server, and so the timeout value is validated\n * before it is ever interpolated into a statement.\n *\n * MySQL specifics:\n * - `SET TRANSACTION ISOLATION LEVEL <level>` applies to the NEXT transaction\n * started in the session, so the adapter issues it BEFORE\n * `conn.beginTransaction()` — never inside the open transaction.\n * - MySQL has no general per-transaction statement timeout.\n * `MAX_EXECUTION_TIME` is the closest tool and it bounds **SELECT statements\n * only** — see {@link maxExecutionTimeStatement}.\n */\n\nimport type { IsolationLevel, TransactionOptions } from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n/** VibeORM isolation level → MySQL isolation level keyword. */\nexport const ISOLATION_LEVEL_SQL: Readonly<Record<IsolationLevel, string>> = Object.freeze({\n ReadCommitted: \"READ COMMITTED\",\n RepeatableRead: \"REPEATABLE READ\",\n Serializable: \"SERIALIZABLE\",\n});\n\n/**\n * `SET TRANSACTION ISOLATION LEVEL <level>` — scoped to the next transaction\n * in the session, which is why the adapter sends it before\n * `conn.beginTransaction()` on the pinned connection.\n *\n * Observability trap (verified live on MySQL 8.4): the one-shot value IS\n * applied to the next transaction — `performance_schema.\n * events_transactions_current` reports the requested level — but\n * `@@transaction_isolation` NEVER reflects it; that variable keeps answering\n * the session value both before and inside the transaction. Tests must assert\n * the level's behavior (e.g. SERIALIZABLE turning plain SELECTs into locking\n * reads), not the variable — see tests/live.test.ts.\n */\nexport function setIsolationLevelStatement(params: { isolationLevel: IsolationLevel }): string {\n return `SET TRANSACTION ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[params.isolationLevel]}`;\n}\n\n/**\n * `SET SESSION MAX_EXECUTION_TIME = <ms>` — the closest MySQL gets to a\n * transaction timeout, and the semantics differ LOUDLY from postgres:\n *\n * **`MAX_EXECUTION_TIME` bounds SELECT statements only.** INSERT / UPDATE /\n * DELETE / DDL inside the transaction run unbounded; MySQL simply has no\n * per-statement timeout for mutations. An over-long SELECT is interrupted with\n * errno 3024 (`ER_QUERY_TIMEOUT`), mapped to `VIBE_TRANSACTION` with\n * `meta.reason = \"timeout\"`. The setting is session-scoped (not\n * transaction-scoped), so the adapter resets it to DEFAULT in `finally` before\n * the connection returns to the pool.\n *\n * One more caveat (verified live on MySQL 8.4): `SELECT SLEEP(n)` cannot\n * observe the bound — an interrupted `SLEEP()` swallows the kill and returns\n * `1` with NO error, so the statement comes back \"successfully\" at ~timeout.\n * Only a SELECT doing real work is cancelled with errno 3024.\n *\n * @throws VibeError `VIBE_VALIDATION` when the timeout is not a non-negative\n * integer — the value is interpolated into SQL, so it is never trusted blindly.\n */\nexport function maxExecutionTimeStatement(params: { timeout: number }): string {\n const { timeout } = params;\n if (!Number.isInteger(timeout) || timeout < 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,\n meta: { timeout },\n });\n }\n return `SET SESSION MAX_EXECUTION_TIME = ${timeout}`;\n}\n\n/**\n * Restores the session default after a transaction that set a timeout. Issued\n * in `finally`; if it fails the adapter destroys the connection instead of\n * releasing it, so a poisoned session never returns to the pool.\n */\nexport const RESET_MAX_EXECUTION_TIME_SQL: string = \"SET SESSION MAX_EXECUTION_TIME = DEFAULT\";\n\n// ─── Raw transaction control (session affinity) ─────────────────\n\n/** How a raw statement steers the adapter's session-affine raw transaction. */\nexport type RawTransactionControl = \"open\" | \"close\" | null;\n\nconst RAW_OPEN_PATTERN: RegExp = /^\\s*(?:begin|start\\s+transaction)\\b[^;]*;?\\s*$/i;\nconst RAW_CLOSE_PATTERN: RegExp = /^\\s*(?:commit|rollback)(?:\\s+work)?\\s*;?\\s*$/i;\n\n/**\n * Classify a raw statement as transaction control. A raw `BEGIN` … `COMMIT`\n * sequence over `executeUnsafe` is sound only when every statement in between\n * rides the SAME pooled connection, so the adapter pins one connection for\n * the whole raw transaction (\"open\" checks out, \"close\" releases). MySQL DDL\n * auto-commits, so @vibeorm/migrate never wraps here — this exists for raw\n * user sequences and keeps the three pool adapters' semantics identical.\n *\n * Deliberately strict: only PURE single-statement control text matches.\n * Compound scripts, `ROLLBACK TO SAVEPOINT` and `COMMIT AND CHAIN` (which\n * keeps a transaction open) never engage the pinning.\n */\nexport function classifyRawTransactionControl(params: { text: string }): RawTransactionControl {\n if (RAW_OPEN_PATTERN.test(params.text)) return \"open\";\n if (RAW_CLOSE_PATTERN.test(params.text)) return \"close\";\n return null;\n}\n\n// ─── Nested transaction options (the cross-adapter contract) ──────\n\n/**\n * Refuse options on a NESTED `transaction()` call — the same contract on every\n * adapter (see the `DatabaseAdapter.transaction` JSDoc in @vibeorm/runtime):\n * a nested transaction is a SAVEPOINT, and a savepoint can neither change the\n * isolation level of the transaction it joins nor enforce its own timeout.\n * Silently dropping the option (the pre-#3 behaviour) hid exactly that.\n *\n * @throws VibeError `VIBE_VALIDATION` when `isolationLevel` or `timeout` is\n * present (an `undefined` or empty options object is accepted).\n */\nexport function refuseNestedTransactionOptions(params: {\n options?: TransactionOptions;\n provider: string;\n}): void {\n const { options, provider } = params;\n if (options === undefined) return;\n\n const passed: string[] = [];\n if (options.isolationLevel !== undefined) passed.push(\"isolationLevel\");\n if (options.timeout !== undefined) passed.push(\"timeout\");\n if (passed.length === 0) return;\n\n const meta: Record<string, unknown> = { provider, nested: true };\n if (options.isolationLevel !== undefined) meta.isolationLevel = options.isolationLevel;\n if (options.timeout !== undefined) meta.timeout = options.timeout;\n\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `nested transactions run as savepoints and cannot honor ${passed.join(\" or \")} — a savepoint cannot change the isolation level of the transaction it joins,
|
|
13
|
+
"/**\n * Transaction-control SQL text (mysql dialect).\n *\n * Kept apart from the adapter so the mapping from `TransactionOptions` to SQL\n * is unit-testable without a server, and so the timeout value is validated\n * before it is ever interpolated into a statement.\n *\n * MySQL specifics:\n * - `SET TRANSACTION ISOLATION LEVEL <level>` applies to the NEXT transaction\n * started in the session, so the adapter issues it BEFORE\n * `conn.beginTransaction()` — never inside the open transaction.\n * - MySQL has no general per-transaction statement timeout.\n * `MAX_EXECUTION_TIME` is the closest tool and it bounds **SELECT statements\n * only** — see {@link maxExecutionTimeStatement}.\n */\n\nimport type { IsolationLevel, TransactionOptions } from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n/** VibeORM isolation level → MySQL isolation level keyword. */\nexport const ISOLATION_LEVEL_SQL: Readonly<Record<IsolationLevel, string>> = Object.freeze({\n ReadCommitted: \"READ COMMITTED\",\n RepeatableRead: \"REPEATABLE READ\",\n Serializable: \"SERIALIZABLE\",\n});\n\n/**\n * `SET TRANSACTION ISOLATION LEVEL <level>` — scoped to the next transaction\n * in the session, which is why the adapter sends it before\n * `conn.beginTransaction()` on the pinned connection.\n *\n * Observability trap (verified live on MySQL 8.4): the one-shot value IS\n * applied to the next transaction — `performance_schema.\n * events_transactions_current` reports the requested level — but\n * `@@transaction_isolation` NEVER reflects it; that variable keeps answering\n * the session value both before and inside the transaction. Tests must assert\n * the level's behavior (e.g. SERIALIZABLE turning plain SELECTs into locking\n * reads), not the variable — see tests/live.test.ts.\n */\nexport function setIsolationLevelStatement(params: { isolationLevel: IsolationLevel }): string {\n return `SET TRANSACTION ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[params.isolationLevel]}`;\n}\n\n/**\n * `SET SESSION MAX_EXECUTION_TIME = <ms>` — the closest MySQL gets to a\n * transaction timeout, and the semantics differ LOUDLY from postgres:\n *\n * **`MAX_EXECUTION_TIME` bounds SELECT statements only.** INSERT / UPDATE /\n * DELETE / DDL inside the transaction run unbounded; MySQL simply has no\n * per-statement timeout for mutations. An over-long SELECT is interrupted with\n * errno 3024 (`ER_QUERY_TIMEOUT`), mapped to `VIBE_TRANSACTION` with\n * `meta.reason = \"timeout\"`. The setting is session-scoped (not\n * transaction-scoped), so the adapter resets it to DEFAULT in `finally` before\n * the connection returns to the pool.\n *\n * One more caveat (verified live on MySQL 8.4): `SELECT SLEEP(n)` cannot\n * observe the bound — an interrupted `SLEEP()` swallows the kill and returns\n * `1` with NO error, so the statement comes back \"successfully\" at ~timeout.\n * Only a SELECT doing real work is cancelled with errno 3024.\n *\n * @throws VibeError `VIBE_VALIDATION` when the timeout is not a non-negative\n * integer — the value is interpolated into SQL, so it is never trusted blindly.\n */\nexport function maxExecutionTimeStatement(params: { timeout: number }): string {\n const { timeout } = params;\n if (!Number.isInteger(timeout) || timeout < 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,\n meta: { timeout },\n });\n }\n return `SET SESSION MAX_EXECUTION_TIME = ${timeout}`;\n}\n\n/**\n * Restores the session default after a transaction that set a timeout. Issued\n * in `finally`; if it fails the adapter destroys the connection instead of\n * releasing it, so a poisoned session never returns to the pool.\n */\nexport const RESET_MAX_EXECUTION_TIME_SQL: string = \"SET SESSION MAX_EXECUTION_TIME = DEFAULT\";\n\n// ─── Raw transaction control (session affinity) ─────────────────\n\n/** How a raw statement steers the adapter's session-affine raw transaction. */\nexport type RawTransactionControl = \"open\" | \"close\" | null;\n\nconst RAW_OPEN_PATTERN: RegExp = /^\\s*(?:begin|start\\s+transaction)\\b[^;]*;?\\s*$/i;\nconst RAW_CLOSE_PATTERN: RegExp = /^\\s*(?:commit|rollback)(?:\\s+work)?\\s*;?\\s*$/i;\n\n/**\n * Classify a raw statement as transaction control. A raw `BEGIN` … `COMMIT`\n * sequence over `executeUnsafe` is sound only when every statement in between\n * rides the SAME pooled connection, so the adapter pins one connection for\n * the whole raw transaction (\"open\" checks out, \"close\" releases). MySQL DDL\n * auto-commits, so @vibeorm/migrate never wraps here — this exists for raw\n * user sequences and keeps the three pool adapters' semantics identical.\n *\n * Deliberately strict: only PURE single-statement control text matches.\n * Compound scripts, `ROLLBACK TO SAVEPOINT` and `COMMIT AND CHAIN` (which\n * keeps a transaction open) never engage the pinning.\n */\nexport function classifyRawTransactionControl(params: { text: string }): RawTransactionControl {\n if (RAW_OPEN_PATTERN.test(params.text)) return \"open\";\n if (RAW_CLOSE_PATTERN.test(params.text)) return \"close\";\n return null;\n}\n\n// ─── Nested transaction options (the cross-adapter contract) ──────\n\n/**\n * Refuse options on a NESTED `transaction()` call — the same contract on every\n * adapter (see the `DatabaseAdapter.transaction` JSDoc in @vibeorm/runtime):\n * a nested transaction is a SAVEPOINT, and a savepoint can neither change the\n * isolation level of the transaction it joins nor enforce its own timeout.\n * Silently dropping the option (the pre-#3 behaviour) hid exactly that.\n *\n * @throws VibeError `VIBE_VALIDATION` when `isolationLevel` or `timeout` is\n * present (an `undefined` or empty options object is accepted).\n */\nexport function refuseNestedTransactionOptions(params: {\n options?: TransactionOptions;\n provider: string;\n}): void {\n const { options, provider } = params;\n if (options === undefined) return;\n\n const passed: string[] = [];\n if (options.isolationLevel !== undefined) passed.push(\"isolationLevel\");\n if (options.timeout !== undefined) passed.push(\"timeout\");\n if (options.deadline !== undefined) passed.push(\"deadline\");\n if (passed.length === 0) return;\n\n const meta: Record<string, unknown> = { provider, nested: true };\n if (options.isolationLevel !== undefined) meta.isolationLevel = options.isolationLevel;\n if (options.timeout !== undefined) meta.timeout = options.timeout;\n if (options.deadline !== undefined) meta.deadline = options.deadline;\n\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `nested transactions run as savepoints and cannot honor ${passed.join(\" or \")} — a savepoint cannot change the isolation level of the transaction it joins, a nested timeout is not enforceable, and a nested deadline would need its own session setting on a connection it does not own (it inherits the top-level one instead); pass options on the top-level $transaction`,\n meta,\n });\n}\n"
|
|
11
14
|
],
|
|
12
|
-
"mappings": "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",
|
|
13
|
-
"debugId": "
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "C68F0F22780DCB5D64756E2164756E21",
|
|
14
17
|
"names": []
|
|
15
18
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw-path `?` placeholder analysis for mysql2's PREPARED protocol.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. F01 moved raw calls that carry parameters off mysql2's text
|
|
5
|
+
* protocol (which interpolates values client-side and cannot know the server's
|
|
6
|
+
* `sql_mode`) onto its prepared protocol. The prepared protocol types a JS
|
|
7
|
+
* **number** in a way the server refuses in a ROW-COUNT position:
|
|
8
|
+
*
|
|
9
|
+
* execute("SELECT 1 LIMIT ?", [2]) → errno 1210 ER_WRONG_ARGUMENTS
|
|
10
|
+
* execute("SELECT 1 LIMIT ?", ["2"]) → OK
|
|
11
|
+
* execute("SELECT ? AS a", [2]) → OK
|
|
12
|
+
*
|
|
13
|
+
* so the ONE narrow repair is: bind a row-count placeholder's integer as its
|
|
14
|
+
* decimal TEXT, and leave every other binding byte-for-byte as it was.
|
|
15
|
+
* Stringifying an integer at a `LIMIT`/`OFFSET` position is lossless — MySQL
|
|
16
|
+
* parses it back to the same unsigned integer — while stringifying anywhere
|
|
17
|
+
* else would change the wire type of ordinary comparisons.
|
|
18
|
+
*
|
|
19
|
+
* The ORM never emits `LIMIT ?` (the renderer inlines its limits as literal
|
|
20
|
+
* text), so this only ever fires for user-written `$queryRaw` /
|
|
21
|
+
* `$queryRawUnsafe` / `$executeRaw*`.
|
|
22
|
+
*
|
|
23
|
+
* SCANNING IS CONSERVATIVE. Finding row-count placeholders means finding the
|
|
24
|
+
* REAL `?` tokens first, so the scanner skips string literals, backtick-quoted
|
|
25
|
+
* identifiers and all three MySQL comment forms. Two self-checks keep a
|
|
26
|
+
* mis-scan from ever mis-binding: the placeholder count must match the
|
|
27
|
+
* parameter count, and only placeholders in a row-count position are touched.
|
|
28
|
+
* When either check fails the values pass through EXACTLY as they do today.
|
|
29
|
+
*
|
|
30
|
+
* Known scanner limits, all of which fail into that pass-through:
|
|
31
|
+
* - `NO_BACKSLASH_ESCAPES`: a backslash is treated as an escape here, so under
|
|
32
|
+
* that mode a literal ending in `\` can be mis-measured. The count check then
|
|
33
|
+
* disagrees and nothing is transformed.
|
|
34
|
+
* - `/*! … *\/` executable comments are skipped like ordinary comments, so a
|
|
35
|
+
* placeholder inside one is not counted — again a count mismatch.
|
|
36
|
+
*/
|
|
37
|
+
/** Where the real `?` placeholders are, and which of them are row counts. */
|
|
38
|
+
export type ScannedPlaceholders = {
|
|
39
|
+
/** How many real placeholders the statement has. */
|
|
40
|
+
readonly count: number;
|
|
41
|
+
/** 0-based ordinals of the placeholders governed by `LIMIT` / `OFFSET`. */
|
|
42
|
+
readonly rowCountPositions: ReadonlySet<number>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Scan a statement for its real `?` placeholders, marking the ones a `LIMIT` or
|
|
46
|
+
* `OFFSET` keyword governs.
|
|
47
|
+
*
|
|
48
|
+
* The row-count context opens on the word `LIMIT` or `OFFSET` and survives only
|
|
49
|
+
* whitespace, comments, commas, integer literals and further placeholders —
|
|
50
|
+
* exactly the tokens MySQL's `LIMIT {[offset,] count | count OFFSET offset}`
|
|
51
|
+
* grammar allows. Any other word or symbol closes it, so `?` anywhere else is
|
|
52
|
+
* left alone.
|
|
53
|
+
*/
|
|
54
|
+
export declare function scanPlaceholders(params: {
|
|
55
|
+
text: string;
|
|
56
|
+
}): ScannedPlaceholders;
|
|
57
|
+
/**
|
|
58
|
+
* The parameter list to hand mysql2's prepared protocol: identical to the one
|
|
59
|
+
* passed in, except that integers in a `LIMIT` / `OFFSET` position become their
|
|
60
|
+
* decimal text.
|
|
61
|
+
*
|
|
62
|
+
* Returns the SAME array when nothing needs changing, so the ORM's own
|
|
63
|
+
* affected-row path — which never emits a row-count placeholder — pays only a
|
|
64
|
+
* word test.
|
|
65
|
+
*/
|
|
66
|
+
export declare function bindRowCountParams(params: {
|
|
67
|
+
text: string;
|
|
68
|
+
values: unknown[];
|
|
69
|
+
}): unknown[];
|
|
70
|
+
//# sourceMappingURL=placeholders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../src/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG;IAChC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD,CAAC;AA8CF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,mBAAmB,CAwD9E;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG,OAAO,EAAE,CAazF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Savepoint lifetimes inside one top-level transaction (F14).
|
|
3
|
+
*
|
|
4
|
+
* A savepoint stack lives on ONE connection: `ROLLBACK TO SAVEPOINT s` undoes
|
|
5
|
+
* everything done after `s`, including work a LATER savepoint already released.
|
|
6
|
+
* Two `transaction()` callbacks opened from the same handle and overlapping in
|
|
7
|
+
* time therefore share that one stack, and the earlier one's rollback silently
|
|
8
|
+
* erases the later one's already committed work. Unique savepoint names do not
|
|
9
|
+
* help — the names never collided, the lifetimes did.
|
|
10
|
+
*
|
|
11
|
+
* The rule enforced here, on every handle of a transaction:
|
|
12
|
+
*
|
|
13
|
+
* > A handle runs when nothing deeper than itself is open. Otherwise it waits
|
|
14
|
+
* > for the innermost open savepoint to settle — unless the call is being made
|
|
15
|
+
* > from INSIDE that savepoint's own callback, which is genuine nesting and
|
|
16
|
+
* > proceeds immediately.
|
|
17
|
+
*
|
|
18
|
+
* The "from inside" test is an {@link AsyncLocalStorage} lookup, so the common
|
|
19
|
+
* nesting shapes never wait on themselves: a child opened from its parent's
|
|
20
|
+
* callback, and the parent handle used from within an open child's callback,
|
|
21
|
+
* both proceed. Only genuinely concurrent siblings serialize.
|
|
22
|
+
*
|
|
23
|
+
* Not covered: a callback that awaits a sibling it did not start. That
|
|
24
|
+
* arrangement asks for two savepoints to be open at once, which is exactly what
|
|
25
|
+
* loses work; it now blocks instead. Deliberate — see the notes for this fix.
|
|
26
|
+
*/
|
|
27
|
+
import { type SavepointCounter } from "./savepoints.ts";
|
|
28
|
+
/**
|
|
29
|
+
* One handle's position in the savepoint stack of a top-level transaction.
|
|
30
|
+
* `state` is shared by reference across every handle of that transaction.
|
|
31
|
+
*/
|
|
32
|
+
export type SavepointScope = {
|
|
33
|
+
/** Physical savepoint depth; 0 is the top-level transaction itself. */
|
|
34
|
+
readonly depth: number;
|
|
35
|
+
/** Shared state: name allocation plus the innermost open savepoint. */
|
|
36
|
+
readonly state: SavepointState;
|
|
37
|
+
};
|
|
38
|
+
/** Mutable state shared by every scope of one top-level transaction. */
|
|
39
|
+
export type SavepointState = {
|
|
40
|
+
/** Savepoint name counter (shared, so names never collide). */
|
|
41
|
+
readonly counter: SavepointCounter;
|
|
42
|
+
/** The innermost savepoint currently open, or `null` when none is. */
|
|
43
|
+
open: OpenSavepoint | null;
|
|
44
|
+
};
|
|
45
|
+
/** An open savepoint: its handle, its name, and when it finished. */
|
|
46
|
+
type OpenSavepoint = {
|
|
47
|
+
readonly scope: SavepointScope;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly parent: OpenSavepoint | null;
|
|
50
|
+
readonly settled: Promise<void>;
|
|
51
|
+
readonly settle: () => void;
|
|
52
|
+
};
|
|
53
|
+
/** Fresh scope for one top-level transaction: depth 0, nothing open. */
|
|
54
|
+
export declare function createSavepointScope(): SavepointScope;
|
|
55
|
+
/**
|
|
56
|
+
* Run a top-level transaction callback inside its scope's async context, so
|
|
57
|
+
* nested calls made from it can be told apart from calls made inside an open
|
|
58
|
+
* child.
|
|
59
|
+
*/
|
|
60
|
+
export declare function runRootScope<T>(params: {
|
|
61
|
+
scope: SavepointScope;
|
|
62
|
+
body: () => Promise<T>;
|
|
63
|
+
}): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* The wait a statement on `scope` owes before it may run, or `undefined` when
|
|
66
|
+
* it may run now. Returning `undefined` keeps the uncontended path — nothing
|
|
67
|
+
* deeper open — free of an extra promise and microtask.
|
|
68
|
+
*/
|
|
69
|
+
export declare function scopeTurn(params: {
|
|
70
|
+
scope: SavepointScope;
|
|
71
|
+
}): Promise<void> | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Open a nested savepoint under `scope`, run `body` inside it and release it —
|
|
74
|
+
* rolling back to it if `body` throws, exactly as before this gate existed.
|
|
75
|
+
*
|
|
76
|
+
* `execute` runs the savepoint control SQL (already wrapped in the adapter's
|
|
77
|
+
* own driver-error mapping). Omit it when the driver manages the savepoint
|
|
78
|
+
* itself (bun:sql's `savepoint()`), in which case only the lifetime is gated.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runNestedSavepoint<T>(params: {
|
|
81
|
+
readonly scope: SavepointScope;
|
|
82
|
+
readonly execute?: (statement: string) => Promise<unknown> | unknown;
|
|
83
|
+
readonly body: (scope: SavepointScope) => Promise<T>;
|
|
84
|
+
}): Promise<T>;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=savepoint-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"savepoint-gate.d.ts","sourceRoot":"","sources":["../src/savepoint-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,KAAK,gBAAgB,EAA6C,MAAM,iBAAiB,CAAC;AAInG;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,cAAc,GAAG;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,sEAAsE;IACtE,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,qEAAqE;AACrE,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B,CAAC;AAWF,wEAAwE;AACxE,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAErG;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,cAAc,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAItF;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE;IAClD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACtD,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb"}
|