@alchemy.run/drizzle 0.0.0-stage → 2.0.0-beta.84

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.
@@ -0,0 +1,146 @@
1
+ import { DurableObjectState } from "@alchemy.run/cloudflare/Workers/DurableObjectState";
2
+ import type { SqlMigrations } from "@alchemy.run/cloudflare/Workers/SqlMigrations";
3
+ import * as SqliteDoClient from "@effect/sql-sqlite-do/SqliteClient";
4
+ import type { AnyRelations, EmptyRelations } from "drizzle-orm";
5
+ import * as SQLiteDoDrizzle from "drizzle-orm/effect-sqlite-do";
6
+ import { migrate } from "drizzle-orm/effect-sqlite-do/migrator";
7
+ import * as Context from "effect/Context";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Scheduler from "effect/Scheduler";
11
+
12
+ /**
13
+ * Migrations for {@link DurableObject} — the shape of the `migrations.js`
14
+ * bundle `drizzle-kit generate` emits for `driver: "durable-sqlite"`
15
+ * (each migration's `.sql` file imported as a text module).
16
+ */
17
+ export interface DurableObjectMigrations {
18
+ readonly migrations: Record<string, string>;
19
+ readonly migrationsTable?: string | undefined;
20
+ }
21
+
22
+ export interface DurableObjectConfig<TRelations extends AnyRelations = EmptyRelations> extends Omit<
23
+ SQLiteDoDrizzle.EffectDrizzleSQLiteDoConfig<TRelations>,
24
+ "storage"
25
+ > {
26
+ /**
27
+ * Migrations applied before the database is returned. Prefer a snapshot
28
+ * from `Cloudflare.SqlMigrations(dir)`: SQL is read during construction
29
+ * and applied with Alchemy's `__alchemy_migrations` bookkeeping.
30
+ * The generated Drizzle `migrations.js` input remains supported and uses
31
+ * Drizzle's own migrator; its SQL imports still require loader support.
32
+ */
33
+ readonly migrations?: SqlMigrations | DurableObjectMigrations | undefined;
34
+ }
35
+
36
+ /**
37
+ * Open a Drizzle database over the current Durable Object's SQLite
38
+ * storage using the `drizzle-orm/effect-sqlite-do` integration (driven by
39
+ * `@effect/sql-sqlite-do`'s `SqliteClient`), applying migrations first
40
+ * when provided. `Cloudflare.SqlMigrations` captures a SQL directory during
41
+ * construction without importing `.sql` files. Each instance applies pending
42
+ * files on activation using Alchemy's shared migration history.
43
+ *
44
+ * Every query is an Effect with a typed error channel — drizzle's
45
+ * `EffectDrizzleQueryError` (query + params + cause, wrapping the
46
+ * underlying effect-sql `SqlError`) — so failures are handled with
47
+ * `Effect.catchTag` instead of leaking as defects. Transactions add
48
+ * `SqlError` to the union. Opening the db itself never fails: a
49
+ * migration that cannot apply dies, since the instance is unusable
50
+ * without its schema.
51
+ *
52
+ * Yield it in the object's inner (instance) Effect — it runs when the
53
+ * instance activates, before any request reaches its methods:
54
+ *
55
+ * ```typescript
56
+ * // schema.ts
57
+ * import { defineRelations } from "drizzle-orm";
58
+ * import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
59
+ *
60
+ * export const users = sqliteTable("users", {
61
+ * id: integer("id").primaryKey({ autoIncrement: true }),
62
+ * name: text("name").notNull(),
63
+ * });
64
+ *
65
+ * export const posts = sqliteTable("posts", {
66
+ * id: integer("id").primaryKey({ autoIncrement: true }),
67
+ * userId: integer("user_id").notNull().references(() => users.id),
68
+ * title: text("title").notNull(),
69
+ * });
70
+ *
71
+ * export const relations = defineRelations({ users, posts }, (t) => ({
72
+ * users: { posts: t.many.posts() },
73
+ * posts: { author: t.one.users({ from: t.posts.userId, to: t.users.id }) },
74
+ * }));
75
+ * ```
76
+ *
77
+ * ```typescript
78
+ * import * as Drizzle from "@alchemy.run/drizzle/Cloudflare";
79
+ * import { posts, relations, users } from "./schema.ts";
80
+ *
81
+ * export class Users extends Cloudflare.DurableObject<Users>()(
82
+ * "Users",
83
+ * Effect.gen(function* () {
84
+ * const migrations = yield* Cloudflare.SqlMigrations("./drizzle");
85
+ * return Effect.gen(function* () {
86
+ * const db = yield* Drizzle.DurableObject({ migrations, relations });
87
+ *
88
+ * return {
89
+ * addUser: (name: string) => db.insert(users).values({ name }),
90
+ * listUsers: () => db.select().from(users),
91
+ * listUsersWithPosts: () =>
92
+ * db.query.users.findMany({ with: { posts: true } }),
93
+ * // typed error handling per operation:
94
+ * tryAddUser: (name: string) =>
95
+ * db
96
+ * .insert(users)
97
+ * .values({ name })
98
+ * .pipe(
99
+ * Effect.catchTag("EffectDrizzleQueryError", () =>
100
+ * Effect.succeed(undefined),
101
+ * ),
102
+ * ),
103
+ * };
104
+ * });
105
+ * }),
106
+ * ) {}
107
+ * ```
108
+ *
109
+ * @binding
110
+ */
111
+ export const DurableObject = Effect.fn("Drizzle.DurableObject")(function* <
112
+ TRelations extends AnyRelations = EmptyRelations,
113
+ >(config?: DurableObjectConfig<TRelations>) {
114
+ const state = yield* DurableObjectState;
115
+ const storage = state.raw.storage;
116
+ const { migrations, ...drizzleConfig } = config ?? {};
117
+ // Built on the ambient (instance) Scope — the client wraps the DO's
118
+ // local SQLite storage, so there is no disposable resource behind it.
119
+ const services = yield* Layer.build(SqliteDoClient.layer({ storage }));
120
+ const client = Context.get(services, SqliteDoClient.SqliteClient);
121
+ const original = client.withTransaction;
122
+ const scheduler = new Scheduler.MixedScheduler("sync");
123
+ const withTransaction: typeof client.withTransaction = (body) =>
124
+ original(body).pipe(Effect.provideService(Scheduler.Scheduler, scheduler));
125
+ // A parent input-gate timer can block later transaction timers. Yield through
126
+ // microtasks while retaining the adapter's client, SQL permit, and context.
127
+ yield* Effect.sync(() => Object.assign(client, { withTransaction }));
128
+ const db = yield* SQLiteDoDrizzle.makeWithDefaults({
129
+ ...(drizzleConfig as Omit<SQLiteDoDrizzle.EffectDrizzleSQLiteDoConfig<TRelations>, "storage">),
130
+ storage,
131
+ }).pipe(Effect.provideContext(services));
132
+ if (migrations !== undefined && "_tag" in migrations) {
133
+ yield* migrations.apply().pipe(Effect.orDie);
134
+ } else if (migrations !== undefined) {
135
+ // A migration that cannot apply leaves the instance unusable — there
136
+ // is no meaningful recovery at init, so it dies rather than forcing
137
+ // every caller to handle (or orDie) an error channel.
138
+ yield* migrate(db, {
139
+ migrations: migrations.migrations,
140
+ ...(migrations.migrationsTable !== undefined
141
+ ? { migrationsTable: migrations.migrationsTable }
142
+ : {}),
143
+ }).pipe(Effect.orDie);
144
+ }
145
+ return db;
146
+ });
package/src/D1.ts ADDED
@@ -0,0 +1,77 @@
1
+ import type { D1DatabaseSource } from "@alchemy.run/cloudflare/D1/SQL";
2
+ import * as D1Client from "@effect/sql-d1/D1Client";
3
+ import { makeExecutionMemo } from "alchemy/Runtime/ExecutionMemo";
4
+ import { proxyChain } from "alchemy/Util/proxy-chain";
5
+ import type { AnyRelations, EmptyRelations } from "drizzle-orm";
6
+ import type { EffectSQLiteD1Database } from "drizzle-orm/effect-d1";
7
+ import * as SQLiteD1Drizzle from "drizzle-orm/effect-d1";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+
11
+ /**
12
+ * Open a Drizzle database over a Cloudflare D1 binding using the
13
+ * `drizzle-orm/effect-d1` integration (which drives queries through
14
+ * `@effect/sql-d1`'s `D1Client`).
15
+ *
16
+ * Accepts the client returned by `Cloudflare.D1.QueryDatabase(db)` — or its
17
+ * `raw` effect directly — and returns a chainable Proxy over
18
+ * `EffectSQLiteD1Database` (via `proxyChain`): every property read records a
19
+ * step, every call records args, and the chain is replayed against the
20
+ * resolved drizzle db when it's finally yielded as an Effect. Callers don't
21
+ * need a separate `yield* conn` step:
22
+ *
23
+ * ```typescript
24
+ * const d1 = yield* Cloudflare.D1.QueryDatabase(Db);
25
+ * const db = yield* Drizzle.D1(d1, { relations });
26
+ *
27
+ * fetch: Effect.gen(function* () {
28
+ * const rows = yield* db.select().from(users);
29
+ * });
30
+ * ```
31
+ *
32
+ * The client build is deferred until the first query and memoized on the
33
+ * current execution's `Scope` (via {@link makeExecutionMemo}), so the
34
+ * `D1Client` (and its prepared-statement cache) is built at most once per
35
+ * execution — a Worker `fetch`/`queue`/`scheduled` event, a Durable Object
36
+ * call, or a Workflow run — and reused across every query in that execution.
37
+ * Resolving the binding is likewise deferred, so deploy / plan-time
38
+ * invocations (where `WorkerEnvironment` isn't provided) never touch D1.
39
+ *
40
+ * The client is built against that same execution scope, so its finalizer
41
+ * fires when the scope closes — when the request / run settles, not when the
42
+ * Worker's isolate-lifetime init completes. Wrapping queries in a nested
43
+ * `Effect.scoped` narrows both the memo and the client's lifetime to that
44
+ * block: memo key and finalizer target are always the same scope object, so
45
+ * they cannot disagree.
46
+ *
47
+ * @binding
48
+ */
49
+ export const D1 = <TRelations extends AnyRelations = EmptyRelations, E = never, R = never>(
50
+ database: D1DatabaseSource<E, R>,
51
+ config?: SQLiteD1Drizzle.EffectDrizzleSQLiteD1Config<TRelations>,
52
+ ) =>
53
+ Effect.map(
54
+ makeExecutionMemo(
55
+ Effect.gen(function* () {
56
+ const db = yield* Effect.isEffect(database) ? database : database.raw;
57
+ const d1Ctx = yield* Layer.build(D1Client.layer({ db }));
58
+ return yield* SQLiteD1Drizzle.makeWithDefaults(
59
+ config ?? ({} as SQLiteD1Drizzle.EffectDrizzleSQLiteD1Config<TRelations>),
60
+ ).pipe(Effect.provideContext(d1Ctx));
61
+ }),
62
+ ),
63
+ (db) =>
64
+ proxyChain<
65
+ EffectSQLiteD1Database<TRelations> & {
66
+ $client: D1Client.D1Client;
67
+ }
68
+ >(
69
+ db as Effect.Effect<
70
+ EffectSQLiteD1Database<TRelations> & {
71
+ $client: D1Client.D1Client;
72
+ },
73
+ any,
74
+ any
75
+ >,
76
+ ),
77
+ );
package/src/LibSQL.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { LibSQLConfig, LibSQLSource } from "@alchemy.run/sql/SQLite";
2
+ // `@effect/sql-libsql` (and @libsql/client underneath) are optional peers —
3
+ // value imports are deferred to first use so `@alchemy.run/drizzle` resolves
4
+ // without them installed.
5
+ import type * as LibsqlClient from "@effect/sql-libsql/LibsqlClient";
6
+ import { makeExecutionMemo } from "alchemy/Runtime/ExecutionMemo";
7
+ import { proxyChain } from "alchemy/Util/proxy-chain";
8
+ import type { AnyRelations, EmptyRelations } from "drizzle-orm";
9
+ import type { EffectLibsqlDatabase } from "drizzle-orm/effect-libsql";
10
+ import type { EffectDrizzleSQLiteConfig } from "drizzle-orm/sqlite-core/effect/utils";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+
14
+ /**
15
+ * Open a Drizzle database over a libSQL database (e.g. Turso) using the
16
+ * `drizzle-orm/effect-libsql` integration.
17
+ *
18
+ * Accepts the client returned by `Turso.Connect(db)` — or any
19
+ * `{ url, authToken }` — and returns a chainable Proxy over
20
+ * `EffectLibsqlDatabase` (via `proxyChain`): every property read records a
21
+ * step, every call records args, and the chain is replayed against the
22
+ * resolved drizzle db when it's finally yielded as an Effect. Callers don't
23
+ * need a separate `yield* conn` step:
24
+ *
25
+ * ```typescript
26
+ * const conn = yield* Turso.Connect(Db);
27
+ * const db = yield* Drizzle.LibSQL(conn, { relations });
28
+ *
29
+ * fetch: Effect.gen(function* () {
30
+ * const rows = yield* db.select().from(users);
31
+ * });
32
+ * ```
33
+ *
34
+ * The client is built lazily on the first query and memoized on the
35
+ * current execution's `Scope` (via {@link makeExecutionMemo}), so it is
36
+ * created at most once per execution — a Worker `fetch`/`queue`/`scheduled`
37
+ * event, a Durable Object call, a Workflow run, or a Lambda invocation —
38
+ * and closed when that execution settles. Resolving the URL and token is
39
+ * likewise deferred, so deploy / plan-time invocations never connect.
40
+ *
41
+ * `@effect/sql-libsql` client options (e.g. `intMode`) are passed via
42
+ * `config.client`.
43
+ *
44
+ * @binding
45
+ */
46
+ export const LibSQL = <TRelations extends AnyRelations = EmptyRelations, E = never, R = never>(
47
+ source: LibSQLSource<E, R>,
48
+ config?: EffectDrizzleSQLiteConfig<TRelations> & {
49
+ /** Overrides for the underlying `@effect/sql-libsql` client. */
50
+ readonly client?: LibSQLConfig;
51
+ },
52
+ ) =>
53
+ Effect.map(
54
+ makeExecutionMemo(
55
+ Effect.gen(function* () {
56
+ const [LibsqlClient, LibsqlDrizzle, { resolveLibSQLConfig }] = yield* Effect.promise(() =>
57
+ Promise.all([
58
+ import("@effect/sql-libsql/LibsqlClient"),
59
+ import("drizzle-orm/effect-libsql"),
60
+ import("@alchemy.run/sql/SQLite"),
61
+ ]),
62
+ );
63
+ const { client, ...drizzleConfig } = config ?? {};
64
+ const ctx = yield* Layer.build(
65
+ LibsqlClient.layer(yield* resolveLibSQLConfig(source, client)),
66
+ );
67
+ return yield* LibsqlDrizzle.makeWithDefaults(
68
+ drizzleConfig as EffectDrizzleSQLiteConfig<TRelations>,
69
+ ).pipe(Effect.provideContext(ctx));
70
+ }),
71
+ ),
72
+ (db) =>
73
+ proxyChain<EffectLibsqlDatabase<TRelations> & { $client: LibsqlClient.LibsqlClient }>(
74
+ db as Effect.Effect<
75
+ EffectLibsqlDatabase<TRelations> & { $client: LibsqlClient.LibsqlClient }
76
+ >,
77
+ ),
78
+ );
package/src/MySQL.ts ADDED
@@ -0,0 +1,86 @@
1
+ import type { MySQLConfig } from "@alchemy.run/sql/MySQL";
2
+ // `@effect/sql-mysql2` (and mysql2 underneath) are optional peers — value
3
+ // imports are deferred to first use so `@alchemy.run/drizzle` resolves without
4
+ // them installed.
5
+ import type * as MysqlClient from "@effect/sql-mysql2/MysqlClient";
6
+ import { makeExecutionMemo } from "alchemy/Runtime/ExecutionMemo";
7
+ import { proxyChain } from "alchemy/Util/proxy-chain";
8
+ import type { AnyRelations, EmptyRelations } from "drizzle-orm";
9
+ import type { EffectMysql2Database } from "drizzle-orm/effect-mysql2";
10
+ import type { EffectDrizzleMySqlConfig } from "drizzle-orm/mysql-core/effect/utils";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import type * as Redacted from "effect/Redacted";
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
+ */
42
+ export const MySQL = <TRelations extends AnyRelations = EmptyRelations, E = never, R = never>(
43
+ connectionString: Effect.Effect<Redacted.Redacted<string>, E, R>,
44
+ config?: EffectDrizzleMySqlConfig<TRelations> & {
45
+ /**
46
+ * Overrides for the underlying `@effect/sql-mysql2` client — pool
47
+ * options (e.g. `poolConfig.ssl` for a direct TLS connection),
48
+ * `disablePreparedStatements`, `maxConnections`, and friends.
49
+ */
50
+ readonly client?: Omit<MySQLConfig, "url">;
51
+ },
52
+ ) =>
53
+ Effect.map(
54
+ makeExecutionMemo(
55
+ Effect.gen(function* () {
56
+ const [MysqlClient, MySqlDrizzle, { resolveMySQLConfig }] = yield* Effect.promise(() =>
57
+ Promise.all([
58
+ import("@effect/sql-mysql2/MysqlClient"),
59
+ import("drizzle-orm/effect-mysql2"),
60
+ import("@alchemy.run/sql/MySQL"),
61
+ ]),
62
+ );
63
+ const { client, ...drizzleConfig } = config ?? {};
64
+ const mysqlCtx = yield* Layer.build(
65
+ MysqlClient.layer(yield* resolveMySQLConfig({ ...client, url: connectionString })),
66
+ );
67
+ return yield* MySqlDrizzle.makeWithDefaults(
68
+ drizzleConfig as EffectDrizzleMySqlConfig<TRelations>,
69
+ ).pipe(Effect.provideContext(mysqlCtx));
70
+ }),
71
+ ),
72
+ (db) =>
73
+ proxyChain<
74
+ EffectMysql2Database<TRelations> & {
75
+ $client: MysqlClient.MysqlClient;
76
+ }
77
+ >(
78
+ db as Effect.Effect<
79
+ EffectMysql2Database<TRelations> & {
80
+ $client: MysqlClient.MysqlClient;
81
+ },
82
+ any,
83
+ any
84
+ >,
85
+ ),
86
+ );
@@ -0,0 +1,100 @@
1
+ import { resolvePoolConfig } from "@alchemy.run/sql/PostgresTls";
2
+ // `@effect/sql-pg` (and pg underneath) are optional peers — value imports
3
+ // are deferred to first use so `@alchemy.run/drizzle` resolves without them
4
+ // installed.
5
+ import type * as PgClient from "@effect/sql-pg/PgClient";
6
+ import { makeExecutionMemo } from "alchemy/Runtime/ExecutionMemo";
7
+ import { proxyChain } from "alchemy/Util/proxy-chain";
8
+ import type { AnyRelations, EmptyRelations } from "drizzle-orm";
9
+ import type { EffectPgDatabase } from "drizzle-orm/effect-postgres";
10
+ import type { EffectDrizzlePgConfig } from "drizzle-orm/pg-core/effect/utils";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import type * as Redacted from "effect/Redacted";
14
+
15
+ /**
16
+ * Open a Drizzle/Postgres database from a connection URL using the
17
+ * `drizzle-orm/effect-postgres` integration.
18
+ *
19
+ * Returns a chainable Proxy over `EffectPgDatabase` (via `proxyChain`) —
20
+ * every property read records a step, every call records args, and the
21
+ * chain is replayed against the resolved drizzle db when it's finally
22
+ * yielded as an Effect. Callers don't need a separate `yield* conn` step:
23
+ *
24
+ * ```typescript
25
+ * const db = yield* Drizzle.Postgres(hd.connectionString);
26
+ *
27
+ * fetch: Effect.gen(function* () {
28
+ * const rows = yield* db.select().from(users);
29
+ * });
30
+ * ```
31
+ *
32
+ * The connect work is deferred until the first query and memoized on the
33
+ * current execution's `Scope` (via {@link makeExecutionMemo}), so the pool
34
+ * is built at most once per execution — a Worker `fetch`/`queue`/`scheduled`
35
+ * event, a Durable Object call, a Workflow run, or a Lambda invocation — and
36
+ * reused across every query and `task` step in that execution. Yielding the
37
+ * connection string is likewise deferred, so deploy / plan-time invocations
38
+ * (where `WorkerEnvironment` isn't provided) never trigger a real connection
39
+ * attempt.
40
+ *
41
+ * The pool is built against that same execution scope, so its `end`
42
+ * finalizer fires when the scope closes — when the request / run settles,
43
+ * not when the Worker's isolate-lifetime init completes. Wrapping queries in
44
+ * a nested `Effect.scoped` narrows both the memo and the pool's lifetime to
45
+ * that block: memo key and finalizer target are always the same scope
46
+ * object, so they cannot disagree.
47
+ *
48
+ * `@effect/sql-pg` client options are passed via `config.client`. Behind a
49
+ * transaction-mode pooler such as Hyperdrive, turn off named prepared
50
+ * statements, since a later query can reach a backend session that never
51
+ * prepared them:
52
+ *
53
+ * ```typescript
54
+ * const db = yield* Drizzle.Postgres(hd.connectionString, {
55
+ * client: { prepare: false },
56
+ * });
57
+ * ```
58
+ *
59
+ * @binding
60
+ */
61
+
62
+ export const Postgres = <TRelations extends AnyRelations = EmptyRelations, E = never, R = never>(
63
+ connectionString: Effect.Effect<Redacted.Redacted<string>, E, R>,
64
+ config?: EffectDrizzlePgConfig<TRelations> & {
65
+ /**
66
+ * Overrides for the underlying `@effect/sql-pg` client — `prepare`,
67
+ * `ssl`, pool limits, and friends. The URL comes from `connectionString`.
68
+ */
69
+ readonly client?: Omit<PgClient.PgPoolConfig, "url">;
70
+ },
71
+ ) =>
72
+ Effect.map(
73
+ makeExecutionMemo(
74
+ Effect.gen(function* () {
75
+ const [PgClient, PgDrizzle] = yield* Effect.promise(() =>
76
+ Promise.all([import("@effect/sql-pg/PgClient"), import("drizzle-orm/effect-postgres")]),
77
+ );
78
+ const url = yield* connectionString;
79
+ const { client, ...drizzleConfig } = config ?? {};
80
+ const pgCtx = yield* Layer.build(PgClient.layer(resolvePoolConfig(url, client)));
81
+ return yield* PgDrizzle.makeWithDefaults(
82
+ drizzleConfig as EffectDrizzlePgConfig<TRelations>,
83
+ ).pipe(Effect.provideContext(pgCtx));
84
+ }),
85
+ ),
86
+ (db) =>
87
+ proxyChain<
88
+ EffectPgDatabase<TRelations> & {
89
+ $client: PgClient.PgClient;
90
+ }
91
+ >(
92
+ db as Effect.Effect<
93
+ EffectPgDatabase<TRelations> & {
94
+ $client: PgClient.PgClient;
95
+ },
96
+ any,
97
+ any
98
+ >,
99
+ ),
100
+ );
@@ -0,0 +1,51 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import * as Layer from "effect/Layer";
3
+ import { Schema, SchemaProvider } from "./Schema.ts";
4
+
5
+ export class Providers extends Provider.ProviderCollection<Providers>()("Drizzle") {}
6
+
7
+ export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
8
+
9
+ /**
10
+ * Build-time providers for managing Drizzle schemas as Alchemy resources.
11
+ * Drizzle.Schema regenerates migration SQL via drizzle-kit's programmatic
12
+ * API on every deploy when the source schema changes.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import * as Alchemy from "alchemy";
17
+ * import * as Cloudflare from "@alchemy.run/cloudflare";
18
+ * import * as Drizzle from "@alchemy.run/drizzle";
19
+ * import * as Neon from "@alchemy.run/neon";
20
+ * import * as Effect from "effect/Effect";
21
+ * import * as Layer from "effect/Layer";
22
+ *
23
+ * export default Alchemy.Stack(
24
+ * "MyStack",
25
+ * {
26
+ * providers: Layer.mergeAll(
27
+ * Cloudflare.providers(),
28
+ * Drizzle.providers(),
29
+ * Neon.providers(),
30
+ * ),
31
+ * state: Alchemy.localState(),
32
+ * },
33
+ * Effect.gen(function* () {
34
+ * const schema = yield* Drizzle.Schema("app-schema", {
35
+ * schema: "./src/schema.ts",
36
+ * });
37
+ * const project = yield* Neon.Project("app-db");
38
+ * const branch = yield* Neon.Branch("app-branch", {
39
+ * project,
40
+ * migrationsDir: schema.out,
41
+ * });
42
+ * return { branchId: branch.branchId };
43
+ * }),
44
+ * );
45
+ * ```
46
+ */
47
+ export const providers = () =>
48
+ Layer.effect(Providers, Provider.collection([Schema])).pipe(
49
+ Layer.provide(SchemaProvider()),
50
+ Layer.orDie,
51
+ );