1// `@effect/sql-mysql2` (and mysql2 underneath) are optional peers — value
2// imports are deferred to first use so `alchemy/Drizzle` resolves without
3// them installed.
4import type * as MysqlClient from "@effect/sql-mysql2/MysqlClient";
5import type { AnyRelations, EmptyRelations } from "drizzle-orm";
6import type { EffectMysql2Database } from "drizzle-orm/effect-mysql2";
7import type { EffectDrizzleMySqlConfig } from "drizzle-orm/mysql-core/effect/utils";
8import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";
10import type * as Redacted from "effect/Redacted";
11import { makeExecutionMemo } from "../Runtime/ExecutionMemo.ts";
12import type { MySQLConfig } from "../SQL/MySQL.ts";
13import { proxyChain } from "../Util/proxy-chain.ts";
14
15/**
16 * Open a Drizzle/MySQL database from a connection URL using the
17 * `drizzle-orm/effect-mysql2` integration.
18 *
19 * ```typescript
20 * const conn = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
21 * const db = yield* Drizzle.MySQL(conn.connectionString, { relations });
22 *
23 * fetch: Effect.gen(function* () {
24 * const rows = yield* db.select().from(users);
25 * });
26 * ```
27 *
28 * The pool opens on the first query of an execution, is reused for every
29 * query in it, and closes when the event settles (see
30 * {@link makeExecutionMemo}); plan/deploy never connect. Workers defaults
31 * ({@link resolveMySQLConfig}) are overridden via `config.client`:
32 *
33 * ```typescript
34 * const db = yield* Drizzle.MySQL(connectionString, {
35 * relations,
36 * client: { poolConfig: { ssl: { rejectUnauthorized: true } } },
37 * });
38 * ```
39 *
40 * @binding
41 */
42export const MySQL = <
43 TRelations extends AnyRelations = EmptyRelations,
44 E = never,
45 R = never,
46>(
47 connectionString: Effect.Effect<Redacted.Redacted<string>, E, R>,
48 config?: EffectDrizzleMySqlConfig<TRelations> & {
49 /**
50 * Overrides for the underlying `@effect/sql-mysql2` client — pool
51 * options (e.g. `poolConfig.ssl` for a direct TLS connection),
52 * `disablePreparedStatements`, `maxConnections`, and friends.
53 */
54 readonly client?: Omit<MySQLConfig, "url">;
55 },
56) =>
57 Effect.map(
58 makeExecutionMemo(
59 Effect.gen(function* () {
60 const [MysqlClient, MySqlDrizzle, { resolveMySQLConfig }] =
61 yield* Effect.promise(() =>
62 Promise.all([
63 import("@effect/sql-mysql2/MysqlClient"),
64 import("drizzle-orm/effect-mysql2"),
65 import("../SQL/MySQL.ts"),
66 ]),
67 );
68 const { client, ...drizzleConfig } = config ?? {};
69 const mysqlCtx = yield* Layer.build(
70 MysqlClient.layer(
71 yield* resolveMySQLConfig({ ...client, url: connectionString }),
72 ),
73 );
74 return yield* MySqlDrizzle.makeWithDefaults(
75 drizzleConfig as EffectDrizzleMySqlConfig<TRelations>,
76 ).pipe(Effect.provideContext(mysqlCtx));
77 }),
78 ),
79 (db) =>
80 proxyChain<
81 EffectMysql2Database<TRelations> & {
82 $client: MysqlClient.MysqlClient;
83 }
84 >(
85 db as Effect.Effect<
86 EffectMysql2Database<TRelations> & {
87 $client: MysqlClient.MysqlClient;
88 }
89 >,
90 ),
91 );