@rdlabo/workers-mysql 0.1.0-beta.pr48.sha371f5792ce8a

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rdlabo-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @rdlabo/workers-mysql
2
+
3
+ MySQL and Hyperdrive infrastructure for Cloudflare Workers without a Hono dependency. Compose
4
+ invocation-scoped primary/replica access, deadlock retries, optional Drizzle helpers, and Node.js
5
+ migration/testing tools while the application keeps its schemas and credentials.
6
+
7
+ The Worker must enable Node.js compatibility because `mysql2` uses Node.js networking APIs:
8
+
9
+ ```toml
10
+ # wrangler.toml
11
+ compatibility_flags = ["nodejs_compat"]
12
+ ```
13
+
14
+ ## Choose an entry point
15
+
16
+ | Import | Responsibility |
17
+ | ---------------------------------- | ------------------------------------------------------------------------------- |
18
+ | `@rdlabo/workers-mysql` | Workers MySQL and Hyperdrive runtime, retry, write-result, and JST wire helpers |
19
+ | `@rdlabo/workers-mysql/drizzle` | Drizzle configuration and JST column helpers |
20
+ | `@rdlabo/workers-mysql/migrations` | Node.js migration and brownfield baseline helpers |
21
+ | `@rdlabo/workers-mysql/testing` | Local MySQL/Drizzle test database and fakes |
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install @rdlabo/workers-mysql
27
+ ```
28
+
29
+ `mysql2` is included as a direct dependency. Add `drizzle-orm` when using `/drizzle` or `/testing`:
30
+
31
+ ```bash
32
+ npm install drizzle-orm
33
+ ```
34
+
35
+ Keeping Drizzle as a peer gives the application and its schemas one type identity.
36
+
37
+ The public connection types use Node.js declarations. `@types/node@>=20.19.43` is a required peer
38
+ (also when deploying to Workers). TypeScript applications should add it directly so its global
39
+ declarations are visible with strict package layouts, including pnpm:
40
+
41
+ ```sh
42
+ npm install -D @types/node@20
43
+ # pnpm users:
44
+ pnpm add -D @types/node@20
45
+ ```
46
+
47
+ Use the matching supported major for your tooling. Automatic peer installation alone may not expose
48
+ these global declarations to the application's TypeScript compiler under pnpm.
49
+
50
+ ## Quick start
51
+
52
+ Create the database inside each Worker invocation. In this fragment, `env` contains the application's
53
+ Hyperdrive bindings and `schema` is its own Drizzle schema:
54
+
55
+ ```ts
56
+ import { createHyperdriveDatabase } from '@rdlabo/workers-mysql';
57
+ import { DRIZZLE_ORM_OPTIONS } from '@rdlabo/workers-mysql/drizzle';
58
+ import { drizzle } from 'drizzle-orm/mysql2';
59
+
60
+ const db = createHyperdriveDatabase({
61
+ primaryHyperdrive: env.PRIMARY,
62
+ replicaHyperdrive: env.REPLICA,
63
+ createOrm: (connection) => drizzle(connection, { schema, ...DRIZZLE_ORM_OPTIONS }),
64
+ });
65
+ ```
66
+
67
+ With `nodejs_compat` enabled, the package root is Workers-runtime-safe and does not load Drizzle or
68
+ Node-only migration code.
69
+
70
+ Fixed `+09:00` storage helpers are a MySQL wire contract. They do not follow IANA display timezones
71
+ from [`@rdlabo/workers-timezone`](https://docs.rdlabo.dev/projects/workers-timezone/docs/readme).
72
+
73
+ ## Hono integration
74
+
75
+ Hono middleware remains an adapter in `@rdlabo/workers-hono-kit/mysql`; the database package itself
76
+ does not depend on Hono.
77
+
78
+ ```ts
79
+ import { createContainerRuntime } from '@rdlabo/workers-hono-kit/mysql';
80
+ ```
81
+
82
+ This adapter is available from Hono kit `0.12.0`. Install both packages:
83
+
84
+ ```sh
85
+ npm install @rdlabo/workers-mysql @rdlabo/workers-hono-kit
86
+ ```
87
+
88
+ ## Documentation
89
+
90
+ - [Runtime](https://docs.rdlabo.dev/projects/workers-mysql/docs/runtime) — request lifetime, primary/replica reads, and retry safety.
91
+ - [Drizzle and dates](https://docs.rdlabo.dev/projects/workers-mysql/docs/drizzle) — schema ownership, optional peer, and fixed-JST storage.
92
+ - [Migrations and testing](https://docs.rdlabo.dev/projects/workers-mysql/docs/tooling) — Node.js tooling and destructive test helpers.
93
+ - [API](https://docs.rdlabo.dev/projects/workers-mysql/docs/api) — public exports by entry point.
94
+ - [Migration](https://docs.rdlabo.dev/projects/workers-mysql/docs/migration) — kit compatibility imports.
95
+
96
+ These guides describe this source revision. Use the matching release tag for an installed version.
97
+
98
+ ## Migrating from workers-hono-kit
99
+
100
+ Kit `0.12.0` changes the import boundaries. Its old `/db` and DB-related `/testing` exports remain
101
+ available as maintained compatibility paths with `@deprecated` notices; there is no planned removal.
102
+ See [Migration](https://docs.rdlabo.dev/projects/workers-mysql/docs/migration) for the import map.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runBaselineCli } from '../dist/baseline-cli.js';
3
+
4
+ await runBaselineCli();
@@ -0,0 +1,2 @@
1
+ /** Run the brownfield migration-baseline command. */
2
+ export declare function runBaselineCli(): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { createConnection } from 'mysql2/promise';
2
+ import { baselineMigrations } from './migrate.js';
3
+ import { resolveDbSecret } from './orm-config.js';
4
+ function arg(name) {
5
+ const index = process.argv.indexOf(`--${name}`);
6
+ return index >= 0 ? process.argv[index + 1] : undefined;
7
+ }
8
+ /** Run the brownfield migration-baseline command. */
9
+ export async function runBaselineCli() {
10
+ const migrationsFolder = arg('migrations') ?? process.env.MIGRATIONS_DIR ?? './drizzle';
11
+ const secret = resolveDbSecret();
12
+ const connectionOptions = secret
13
+ ? {
14
+ host: secret.host,
15
+ port: secret.port,
16
+ user: secret.username,
17
+ password: secret.password,
18
+ database: secret.dbname,
19
+ }
20
+ : {
21
+ host: process.env.DB_HOST ?? '127.0.0.1',
22
+ port: Number(process.env.DB_PORT ?? '3306'),
23
+ user: process.env.DB_USER ?? 'root',
24
+ password: process.env.DB_PASSWORD ?? 'root',
25
+ database: process.env.DB_NAME,
26
+ };
27
+ if (!connectionOptions.database) {
28
+ console.error('[db:baseline] DB_NAME (or DB_SECRET) is required.');
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+ console.log('[db:baseline] database target configured.');
33
+ const db = await createConnection(connectionOptions);
34
+ await baselineMigrations({ db, migrationsFolder })
35
+ .then((result) => {
36
+ if (result.status === 'already-baselined') {
37
+ console.log(`[db:baseline] already baselined (${result.tag}, created_at=${result.when}). no-op.`);
38
+ return;
39
+ }
40
+ console.log(`[db:baseline] inserted baseline marker for ${result.tag} (created_at=${result.when}). ` +
41
+ '0000 is now recorded as applied; future migrations will run.');
42
+ }, (error) => {
43
+ console.error('[db:baseline] failed:', error instanceof Error ? error.message : error);
44
+ process.exitCode = 1;
45
+ })
46
+ .finally(() => db.end());
47
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SQL expression for `ON UPDATE CURRENT_TIMESTAMP` (the MySQL session clock).
3
+ * customType columns have no `.onUpdateNow()`, so pair this with `.$onUpdateFn(() => jstOnUpdateNow(fsp))`.
4
+ *
5
+ * @param fsp - optional fractional-seconds precision; when provided, emits `CURRENT_TIMESTAMP(fsp)`.
6
+ */
7
+ export declare const jstOnUpdateNow: (fsp?: number) => import("drizzle-orm").SQL<unknown>;
8
+ /** MySQL `timestamp` — pass-through. A `Date` is formatted as JST by mysql2 via the connection `timezone:'+09:00'`. */
9
+ export declare const jstTimestamp: (name: string, opts?: {
10
+ fsp?: number;
11
+ }) => import("drizzle-orm/mysql-core").MySqlCustomColumnBuilder<{
12
+ name: string;
13
+ dataType: "custom";
14
+ columnType: "MySqlCustomColumn";
15
+ data: string | Date;
16
+ driverParam: string | Date;
17
+ enumValues: undefined;
18
+ }>;
19
+ /** MySQL `datetime` — same pass-through policy as {@link jstTimestamp}. */
20
+ export declare const jstDatetime: (name: string, opts?: {
21
+ fsp?: number;
22
+ }) => import("drizzle-orm/mysql-core").MySqlCustomColumnBuilder<{
23
+ name: string;
24
+ dataType: "custom";
25
+ columnType: "MySqlCustomColumn";
26
+ data: string | Date;
27
+ driverParam: string | Date;
28
+ enumValues: undefined;
29
+ }>;
30
+ /** MySQL `date` — on INSERT/UPDATE, normalizes ISO / empty strings to `YYYY-MM-DD` (via `toDriver`). */
31
+ export declare const jstDate: (name: string) => import("drizzle-orm/mysql-core").MySqlCustomColumnBuilder<{
32
+ name: string;
33
+ dataType: "custom";
34
+ columnType: "MySqlCustomColumn";
35
+ data: string | null;
36
+ driverParam: string | null;
37
+ enumValues: undefined;
38
+ }>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared Drizzle column helpers. Removes the need for a thin `custom-types.ts` / `columns.ts` wrapper
3
+ * in each repo.
4
+ *
5
+ * @remarks
6
+ * `drizzle-orm` is a **peer** (the consumer resolves a single copy); this package does not bundle it. The
7
+ * return types are the `customType` inference as-is (`MySqlCustomColumnBuilder<…>`) with no `any`, so
8
+ * the column's semantic type (`string | Date`, etc.) propagates to the consumer table's `$inferSelect`.
9
+ *
10
+ * **Precondition (a single drizzle copy)**: Drizzle's `SQL` is a **nominal** type carrying a private
11
+ * field `shouldInlineParams`, so if the package and consumer resolve different copies of drizzle,
12
+ * `jstTimestamp(…).default(sql\`…\`)` fails with `TS2345: separate declarations of a private property
13
+ * 'shouldInlineParams'`. A direct `file:` link can nest a second copy of drizzle under the package.
14
+ * Pin `drizzle-orm` to the consumer's **own single copy** with tsconfig
15
+ * `paths` (see the "Drizzle column helpers" section of the README). The published package (a single
16
+ * copy) is already unified.
17
+ *
18
+ * **DEFAULT / ON UPDATE CURRENT_TIMESTAMP** is a server-side default (an INSERT / UPDATE that omits the
19
+ * column). The connection's `timezone:'+09:00'` ({@link hyperdriveConnectionOptions}) only applies when
20
+ * the **app binds a `Date`**. Do not conflate the two (see the `datetime-wire` / `drizzle-smoke` JST
21
+ * tests).
22
+ */
23
+ import { sql } from 'drizzle-orm';
24
+ import { customType } from 'drizzle-orm/mysql-core';
25
+ import { jstDateParams, jstDatetimeParams, jstTimestampParams } from './jst.js';
26
+ /**
27
+ * SQL expression for `ON UPDATE CURRENT_TIMESTAMP` (the MySQL session clock).
28
+ * customType columns have no `.onUpdateNow()`, so pair this with `.$onUpdateFn(() => jstOnUpdateNow(fsp))`.
29
+ *
30
+ * @param fsp - optional fractional-seconds precision; when provided, emits `CURRENT_TIMESTAMP(fsp)`.
31
+ */
32
+ export const jstOnUpdateNow = (fsp) => fsp != null ? sql `(CURRENT_TIMESTAMP(${sql.raw(String(fsp))}))` : sql `(CURRENT_TIMESTAMP)`;
33
+ /** MySQL `timestamp` — pass-through. A `Date` is formatted as JST by mysql2 via the connection `timezone:'+09:00'`. */
34
+ export const jstTimestamp = (name, opts) => customType(jstTimestampParams(opts?.fsp))(name);
35
+ /** MySQL `datetime` — same pass-through policy as {@link jstTimestamp}. */
36
+ export const jstDatetime = (name, opts) => customType(jstDatetimeParams(opts?.fsp))(name);
37
+ /** MySQL `date` — on INSERT/UPDATE, normalizes ISO / empty strings to `YYYY-MM-DD` (via `toDriver`). */
38
+ export const jstDate = (name) => customType(jstDateParams())(name);
@@ -0,0 +1,76 @@
1
+ import type { Connection } from 'mysql2/promise';
2
+ /** Minimal structural shape retained by the compatibility connection-lifecycle API. */
3
+ export interface ExecutionContextLike {
4
+ waitUntil(promise: Promise<unknown>): void;
5
+ }
6
+ /**
7
+ * Minimal structural shape of a Cloudflare Hyperdrive binding.
8
+ *
9
+ * @remarks
10
+ * Declared structurally to avoid a dependency on `@cloudflare/workers-types`; any object with these
11
+ * connection fields satisfies it.
12
+ */
13
+ export interface HyperdriveLike {
14
+ /** Database host to connect to. */
15
+ host: string;
16
+ /** Database user. */
17
+ user: string;
18
+ /** Database password. */
19
+ password: string;
20
+ /** Database name. */
21
+ database: string;
22
+ /** Database port. */
23
+ port: number;
24
+ }
25
+ /**
26
+ * Build mysql2 `createConnection` options from a Hyperdrive binding, applying the package defaults.
27
+ *
28
+ * @remarks
29
+ * Three defaults are applied and can each be overridden via `extra`:
30
+ *
31
+ * - `disableEval: true` — `eval` is unavailable in the Workers runtime, so the driver's eval-based
32
+ * fast paths must be disabled.
33
+ * - `decimalNumbers: true` — return `DECIMAL`/`NEWDECIMAL` columns as JS `number` rather than
34
+ * strings, so raw-SQL reads and Drizzle's inferred types align on a single numeric domain type.
35
+ * This assumes no column's precision exceeds the JS safe-integer range.
36
+ * - `timezone: '+09:00'` — interpret and serialize JavaScript `Date` values as JST. This is a
37
+ * mysql2 client-side conversion option; it does not issue `SET time_zone` or change the MySQL
38
+ * session timezone. Non-JST deployments can override it via `extra: { timezone: '...' }`.
39
+ *
40
+ * @param hyperdrive - the Hyperdrive binding to derive connection fields from.
41
+ * @param extra - additional mysql2 options merged last, overriding the defaults above.
42
+ * @returns a plain options object to pass to mysql2 `createConnection`.
43
+ */
44
+ export declare function hyperdriveConnectionOptions(hyperdrive: HyperdriveLike, extra?: Record<string, unknown>): Record<string, unknown>;
45
+ /**
46
+ * Open primary and replica connections in parallel and run `fn` with them.
47
+ *
48
+ * Cloudflare Workers automatically cleans up connections created during an invocation. Calling
49
+ * `Connection.end()` is unnecessary and can race work registered with `waitUntil`. The `ctx`
50
+ * parameter remains for API compatibility and is intentionally not used for connection teardown.
51
+ *
52
+ * @typeParam T - resolved value produced by `fn`.
53
+ * @param hyperdrives - the primary and replica Hyperdrive bindings to connect to.
54
+ * @param ctx - the request execution context, retained for API compatibility.
55
+ * @param fn - callback invoked with the open `primary` and `replica` connections.
56
+ * @param connectionOptions - extra mysql2 options forwarded to {@link hyperdriveConnectionOptions}.
57
+ * @returns the value resolved by `fn`.
58
+ * @example
59
+ * ```ts
60
+ * const data = await withMysqlConnections(
61
+ * { primary: env.PRIMARY, replica: env.REPLICA },
62
+ * ctx,
63
+ * async ({ primary, replica }) => {
64
+ * const [rows] = await replica.query('SELECT 1');
65
+ * return rows;
66
+ * },
67
+ * );
68
+ * ```
69
+ */
70
+ export declare function withMysqlConnections<T>(hyperdrives: {
71
+ primary: HyperdriveLike;
72
+ replica: HyperdriveLike;
73
+ }, ctx: ExecutionContextLike, fn: (connections: {
74
+ primary: Connection;
75
+ replica: Connection;
76
+ }) => Promise<T>, connectionOptions?: Record<string, unknown>): Promise<T>;
@@ -0,0 +1,67 @@
1
+ import { createConnection } from 'mysql2/promise';
2
+ import { MYSQL_TIMEZONE } from './jst.js';
3
+ /**
4
+ * Build mysql2 `createConnection` options from a Hyperdrive binding, applying the package defaults.
5
+ *
6
+ * @remarks
7
+ * Three defaults are applied and can each be overridden via `extra`:
8
+ *
9
+ * - `disableEval: true` — `eval` is unavailable in the Workers runtime, so the driver's eval-based
10
+ * fast paths must be disabled.
11
+ * - `decimalNumbers: true` — return `DECIMAL`/`NEWDECIMAL` columns as JS `number` rather than
12
+ * strings, so raw-SQL reads and Drizzle's inferred types align on a single numeric domain type.
13
+ * This assumes no column's precision exceeds the JS safe-integer range.
14
+ * - `timezone: '+09:00'` — interpret and serialize JavaScript `Date` values as JST. This is a
15
+ * mysql2 client-side conversion option; it does not issue `SET time_zone` or change the MySQL
16
+ * session timezone. Non-JST deployments can override it via `extra: { timezone: '...' }`.
17
+ *
18
+ * @param hyperdrive - the Hyperdrive binding to derive connection fields from.
19
+ * @param extra - additional mysql2 options merged last, overriding the defaults above.
20
+ * @returns a plain options object to pass to mysql2 `createConnection`.
21
+ */
22
+ export function hyperdriveConnectionOptions(hyperdrive, extra) {
23
+ return {
24
+ host: hyperdrive.host,
25
+ user: hyperdrive.user,
26
+ password: hyperdrive.password,
27
+ database: hyperdrive.database,
28
+ port: hyperdrive.port,
29
+ disableEval: true,
30
+ decimalNumbers: true,
31
+ timezone: MYSQL_TIMEZONE,
32
+ ...extra,
33
+ };
34
+ }
35
+ /**
36
+ * Open primary and replica connections in parallel and run `fn` with them.
37
+ *
38
+ * Cloudflare Workers automatically cleans up connections created during an invocation. Calling
39
+ * `Connection.end()` is unnecessary and can race work registered with `waitUntil`. The `ctx`
40
+ * parameter remains for API compatibility and is intentionally not used for connection teardown.
41
+ *
42
+ * @typeParam T - resolved value produced by `fn`.
43
+ * @param hyperdrives - the primary and replica Hyperdrive bindings to connect to.
44
+ * @param ctx - the request execution context, retained for API compatibility.
45
+ * @param fn - callback invoked with the open `primary` and `replica` connections.
46
+ * @param connectionOptions - extra mysql2 options forwarded to {@link hyperdriveConnectionOptions}.
47
+ * @returns the value resolved by `fn`.
48
+ * @example
49
+ * ```ts
50
+ * const data = await withMysqlConnections(
51
+ * { primary: env.PRIMARY, replica: env.REPLICA },
52
+ * ctx,
53
+ * async ({ primary, replica }) => {
54
+ * const [rows] = await replica.query('SELECT 1');
55
+ * return rows;
56
+ * },
57
+ * );
58
+ * ```
59
+ */
60
+ export async function withMysqlConnections(hyperdrives, ctx, fn, connectionOptions) {
61
+ void ctx;
62
+ const [primary, replica] = await Promise.all([
63
+ createConnection(hyperdriveConnectionOptions(hyperdrives.primary, connectionOptions)),
64
+ createConnection(hyperdriveConnectionOptions(hyperdrives.replica, connectionOptions)),
65
+ ]);
66
+ return fn({ primary, replica });
67
+ }
@@ -0,0 +1,249 @@
1
+ import type { Connection, Pool } from 'mysql2/promise';
2
+ import type { HyperdriveLike } from './connection.js';
3
+ /**
4
+ * Dual-connection data layer that separates reads from writes.
5
+ *
6
+ * @remarks
7
+ * The two sides of the database are deliberately handled differently:
8
+ *
9
+ * - Reads go to the **replica** as raw SQL (`QueryRunner.query`) by default, returning plain rows.
10
+ * Hyperdrive-backed databases also expose `query()` for freshness-sensitive primary SELECTs.
11
+ * - Writes and transactions go to the **primary** through the Drizzle ORM for type safety, but only
12
+ * via `write(fn)` / `transaction(fn)` — the raw query builder is never exposed. The builder is
13
+ * awaited inside those methods, which removes a foot-gun: a Drizzle builder is a lazy thenable, so
14
+ * a bare `return builder` would silently become a no-op.
15
+ *
16
+ * Both sides retry on `ER_LOCK_DEADLOCK`.
17
+ *
18
+ * This package deliberately avoids depending on the type identity of `drizzle-orm`: the consumer creates
19
+ * the ORM instance with its own copy of `drizzle-orm` and passes it in, and {@link Database} is
20
+ * generic over that ORM type (`TDrizzle`). This keeps the ORM's `MySqlTable`/`SQL` brands from
21
+ * clashing even when the package and consumer resolve separate copies of `drizzle-orm`.
22
+ */
23
+ /**
24
+ * Minimal connection interface used for reads.
25
+ *
26
+ * @remarks
27
+ * A mysql2 `Connection` or `Pool` satisfies this structurally.
28
+ */
29
+ export interface QueryRunner {
30
+ /**
31
+ * Run a parameterized SQL query.
32
+ *
33
+ * @param sql - the SQL text, with `?` placeholders for `params`.
34
+ * @param params - optional positional parameters.
35
+ * @returns the driver's raw result (typically `[rows, fields]`).
36
+ */
37
+ query(sql: string, params?: unknown[]): Promise<unknown>;
38
+ }
39
+ /**
40
+ * Extract the transaction-handle type that a Drizzle instance passes to its `.transaction(cb)`
41
+ * callback.
42
+ *
43
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
44
+ */
45
+ export type TxOf<TDrizzle> = TDrizzle extends {
46
+ transaction(cb: (tx: infer Tx) => Promise<unknown>): Promise<unknown>;
47
+ } ? Tx : unknown;
48
+ /**
49
+ * The read/write surface of the data layer.
50
+ *
51
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type used for writes and transactions.
52
+ * @typeParam TTx - the transaction-handle type, inferred from `TDrizzle` by default.
53
+ */
54
+ export interface Database<TDrizzle, TTx = TxOf<TDrizzle>> {
55
+ /**
56
+ * Run a raw SQL read against the replica. Hyperdrive-backed databases retry deadlocks and repeat
57
+ * the SELECT once on a fresh connection after mysql2 reports a fatal connection error.
58
+ *
59
+ * @typeParam T - the row shape.
60
+ * @param sql - the SQL text, with `?` placeholders for `params`.
61
+ * @param params - optional positional parameters.
62
+ * @returns the rows returned by the query.
63
+ */
64
+ read<T>(sql: string, params?: unknown[]): Promise<T[]>;
65
+ /**
66
+ * Run a single INSERT/UPDATE/DELETE against the primary, awaited with deadlock retry.
67
+ *
68
+ * @typeParam T - the value resolved by `fn`.
69
+ * @param fn - callback that receives the Drizzle ORM and returns the awaited write.
70
+ * @returns the value resolved by `fn`.
71
+ */
72
+ write<T>(fn: (dz: TDrizzle) => Promise<T>): Promise<T>;
73
+ /**
74
+ * Run multiple writes inside a single transaction; the whole transaction is retried on deadlock.
75
+ *
76
+ * @typeParam T - the value resolved by `fn`.
77
+ * @param fn - callback that receives the transaction handle and returns the awaited work.
78
+ * @returns the value resolved by `fn`.
79
+ */
80
+ transaction<T>(fn: (tx: TTx) => Promise<T>): Promise<T>;
81
+ }
82
+ /**
83
+ * A {@link Database} that opens its own connections.
84
+ *
85
+ * @remarks
86
+ * Used by variants that open connections internally. Lifecycle behavior depends on the backing
87
+ * implementation: pool-backed databases close their pool, while Hyperdrive-backed databases leave
88
+ * invocation-scoped connection cleanup to the Workers runtime.
89
+ *
90
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
91
+ * @typeParam TTx - the transaction-handle type, inferred from `TDrizzle` by default.
92
+ */
93
+ export interface DisposableDatabase<TDrizzle, TTx = TxOf<TDrizzle>> extends Database<TDrizzle, TTx> {
94
+ /**
95
+ * Release resources owned by the implementation. Hyperdrive-backed databases keep this method as
96
+ * a compatibility no-op; pool-backed databases use it to close their pool.
97
+ *
98
+ * @returns a promise that resolves after implementation-specific cleanup.
99
+ */
100
+ dispose(): Promise<void>;
101
+ }
102
+ /**
103
+ * A Hyperdrive-backed database with an explicit primary query path.
104
+ *
105
+ * @remarks
106
+ * Use `query()` only for SELECTs that require read-after-write consistency or cannot use the
107
+ * configured replica. Fatal connection errors recreate the primary connection and repeat the
108
+ * SELECT at most once. Writes and transactions are never repeated for connection errors.
109
+ * Read-only transactions are serialized on a separately cached primary connection so their
110
+ * snapshot boundaries cannot mix with ordinary primary operations or with each other. Do not call
111
+ * `readTransaction()` recursively from its own callback.
112
+ *
113
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
114
+ * @typeParam TTx - the transaction-handle type, inferred from `TDrizzle` by default.
115
+ */
116
+ export interface HyperdriveDatabase<TDrizzle, TTx = TxOf<TDrizzle>> extends DisposableDatabase<TDrizzle, TTx> {
117
+ /**
118
+ * Run a raw SQL SELECT against the primary database.
119
+ *
120
+ * @typeParam T - the complete rows result type (for example, `User[]`).
121
+ * @param sql - the SQL text, with `?` placeholders for `params`.
122
+ * @param params - optional positional parameters.
123
+ * @returns the rows returned by the query, typed as `T`.
124
+ */
125
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T>;
126
+ /**
127
+ * Run a repeatable-read, consistent snapshot on the primary database.
128
+ *
129
+ * @remarks
130
+ * The callback is repeated at most once on a fresh connection after a fatal connection error.
131
+ * This is safe only because the transaction is declared read-only. Use {@link transaction} for
132
+ * writes; write transactions are never repeated after connection loss. Calls are serialized on
133
+ * one dedicated connection, so recursive `readTransaction()` calls are not supported.
134
+ */
135
+ readTransaction<T>(fn: (reader: ReadTransaction<TTx>) => Promise<T>): Promise<T>;
136
+ }
137
+ /** Primary read-only transaction handles that share one consistent snapshot. */
138
+ export interface ReadTransaction<TTx> {
139
+ /**
140
+ * The consumer's Drizzle transaction handle.
141
+ *
142
+ * @remarks
143
+ * Drizzle's type does not distinguish read-only transactions. MySQL enforces read-only mode at
144
+ * runtime; applications may expose a narrower SELECT-only facade when they need compile-time
145
+ * enforcement.
146
+ */
147
+ orm: TTx;
148
+ /** Run raw SQL on the same primary connection and snapshot. */
149
+ query: <T = unknown>(sql: string, params?: unknown[]) => Promise<T>;
150
+ }
151
+ /**
152
+ * Options for {@link createMysqlDatabase}.
153
+ *
154
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
155
+ */
156
+ export interface CreateMysqlDatabaseOptions<TDrizzle> {
157
+ /**
158
+ * The Drizzle ORM used for writes, created by the consumer with its own `drizzle-orm`
159
+ * (e.g. `drizzle(primary, { schema, ... })`).
160
+ */
161
+ orm: TDrizzle;
162
+ /** The connection used for reads (raw SQL). */
163
+ replica: QueryRunner;
164
+ }
165
+ /**
166
+ * Assemble a {@link Database} from an already-connected ORM and replica.
167
+ *
168
+ * @remarks
169
+ * The caller (typically the worker entry point) owns creating the connections and the ORM, and is
170
+ * responsible for closing the connections; this variant does not manage their lifecycle.
171
+ *
172
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
173
+ * @param options - the write ORM and the read connection.
174
+ * @returns a {@link Database} backed by the supplied ORM and replica.
175
+ * @example
176
+ * ```ts
177
+ * const db = createMysqlDatabase({
178
+ * orm: drizzle(primary, { schema, ...DRIZZLE_ORM_OPTIONS }),
179
+ * replica,
180
+ * });
181
+ * const rows = await db.read<User>('SELECT * FROM users WHERE id = ?', [id]);
182
+ * ```
183
+ */
184
+ export declare function createMysqlDatabase<TDrizzle>(options: CreateMysqlDatabaseOptions<TDrizzle>): Database<TDrizzle>;
185
+ /**
186
+ * Options for {@link createHyperdriveDatabase}.
187
+ *
188
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
189
+ */
190
+ export interface CreateHyperdriveDatabaseOptions<TDrizzle> {
191
+ /** The Hyperdrive binding for the primary (write) connection. */
192
+ primaryHyperdrive: HyperdriveLike;
193
+ /** The Hyperdrive binding for the replica (read) connection. */
194
+ replicaHyperdrive: HyperdriveLike;
195
+ /**
196
+ * Factory that builds the write ORM from the primary connection, using the consumer's
197
+ * `drizzle-orm`.
198
+ */
199
+ createOrm: (primary: Connection) => TDrizzle;
200
+ /**
201
+ * Extra options forwarded to mysql2 `createConnection`, merged on top of the defaults applied by
202
+ * {@link hyperdriveConnectionOptions} (`disableEval: true`, `decimalNumbers: true`, and
203
+ * `timezone: '+09:00'`). Pass a field here to override any of those defaults.
204
+ */
205
+ connectionOptions?: Record<string, unknown>;
206
+ }
207
+ /**
208
+ * Create a {@link HyperdriveDatabase} that lazily opens its connections from Hyperdrive bindings.
209
+ *
210
+ * @remarks
211
+ * Construct one per request. Connections and the ORM are created on first use and reused for the
212
+ * lifetime of the instance. `read()` uses the replica, while `query()` provides an explicit
213
+ * primary SELECT path. Workers automatically cleans up connections at the end of the invocation,
214
+ * so callers do not need to close them manually. Either SELECT path is repeated once on a fresh
215
+ * connection after a fatal mysql2 connection error. Writes and transactions are never repeated
216
+ * for connection errors because their commit state can be ambiguous.
217
+ *
218
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
219
+ * @param options - the primary/replica Hyperdrive bindings, the ORM factory, and connection options.
220
+ * @returns a {@link HyperdriveDatabase} whose compatibility `dispose()` method is a no-op; Workers
221
+ * cleans up its invocation-scoped connections automatically.
222
+ * @example
223
+ * ```ts
224
+ * const db = createHyperdriveDatabase({
225
+ * primaryHyperdrive: env.PRIMARY,
226
+ * replicaHyperdrive: env.REPLICA,
227
+ * createOrm: (primary) => drizzle(primary, { schema, ...DRIZZLE_ORM_OPTIONS }),
228
+ * });
229
+ * await db.write((dz) => dz.insert(users).values(user));
230
+ * ```
231
+ */
232
+ export declare function createHyperdriveDatabase<TDrizzle>(options: CreateHyperdriveDatabaseOptions<TDrizzle>): HyperdriveDatabase<TDrizzle>;
233
+ /**
234
+ * Internal helper that assembles a {@link Database} from an ORM and a replica connection.
235
+ *
236
+ * @typeParam TDrizzle - the consumer's Drizzle ORM type.
237
+ * @param orm - the Drizzle ORM used for writes and transactions.
238
+ * @param replica - the connection used for reads.
239
+ * @returns a {@link Database} wiring reads to `replica` and writes to `orm`, both with deadlock retry.
240
+ * @internal
241
+ */
242
+ export declare function databaseFrom<TDrizzle>(orm: TDrizzle, replica: QueryRunner): Database<TDrizzle>;
243
+ /**
244
+ * Re-export of the mysql2 `Connection` and `Pool` types.
245
+ *
246
+ * @remarks
247
+ * Both are structurally assignable to this package's {@link QueryRunner}.
248
+ */
249
+ export type { Connection, Pool };