@alchemy.run/drizzle 2.0.0-beta.85 → 2.0.0-beta.86

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/src/Postgres.ts CHANGED
@@ -1,25 +1,17 @@
1
- import { resolvePoolConfig } from "@alchemy.run/sql/PostgresTls";
2
1
  // `@effect/sql-pg` (and pg underneath) are optional peers — value imports
3
- // are deferred to first use so `@alchemy.run/drizzle` resolves without them
2
+ // are deferred to the call so `@alchemy.run/drizzle` resolves without them
4
3
  // installed.
5
4
  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
5
  import type { AnyRelations, EmptyRelations } from "drizzle-orm";
9
- import type { EffectPgDatabase } from "drizzle-orm/effect-postgres";
10
6
  import type { EffectDrizzlePgConfig } from "drizzle-orm/pg-core/effect/utils";
11
7
  import * as Effect from "effect/Effect";
12
- import * as Layer from "effect/Layer";
13
8
  import type * as Redacted from "effect/Redacted";
14
9
 
15
10
  /**
16
11
  * Open a Drizzle/Postgres database from a connection URL using the
17
12
  * `drizzle-orm/effect-postgres` integration.
18
13
  *
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:
14
+ * Resolve it once at Construction phase and use it from any handler:
23
15
  *
24
16
  * ```typescript
25
17
  * const db = yield* Drizzle.Postgres(hd.connectionString);
@@ -29,21 +21,18 @@ import type * as Redacted from "effect/Redacted";
29
21
  * });
30
22
  * ```
31
23
  *
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.
24
+ * The database is a real `EffectPgDatabase`. Queries, subqueries, CTEs and
25
+ * `sql` fragments are built by drizzle's own code, with no connection.
26
+ * Running a query opens the pool. The pool is opened at most once per
27
+ * execution — a Worker `fetch`/`queue`/`scheduled` event, a Durable Object
28
+ * call, a Workflow run, or a Lambda invocation — and reused by every query
29
+ * and `task` step in it. The connection string resolves then too, so deploy
30
+ * / plan-time invocations (where `WorkerEnvironment` isn't provided) never
31
+ * connect.
40
32
  *
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.
33
+ * The pool belongs to the execution's `Scope` and closes when the request /
34
+ * run settles. Wrapping queries in a nested `Effect.scoped` gives that block
35
+ * its own pool, closed when the block ends.
47
36
  *
48
37
  * `@effect/sql-pg` client options are passed via `config.client`. Behind a
49
38
  * transaction-mode pooler such as Hyperdrive, turn off named prepared
@@ -58,8 +47,11 @@ import type * as Redacted from "effect/Redacted";
58
47
  *
59
48
  * @binding
60
49
  */
61
-
62
- export const Postgres = <TRelations extends AnyRelations = EmptyRelations, E = never, R = never>(
50
+ export const Postgres = Effect.fn(function* <
51
+ TRelations extends AnyRelations = EmptyRelations,
52
+ E = never,
53
+ R = never,
54
+ >(
63
55
  connectionString: Effect.Effect<Redacted.Redacted<string>, E, R>,
64
56
  config?: EffectDrizzlePgConfig<TRelations> & {
65
57
  /**
@@ -68,33 +60,16 @@ export const Postgres = <TRelations extends AnyRelations = EmptyRelations, E = n
68
60
  */
69
61
  readonly client?: Omit<PgClient.PgPoolConfig, "url">;
70
62
  },
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
- ),
63
+ ) {
64
+ const [{ PgClient }, PgDrizzle, SQL] = yield* Effect.promise(() =>
65
+ Promise.all([
66
+ import("@effect/sql-pg/PgClient"),
67
+ import("drizzle-orm/effect-postgres"),
68
+ import("@alchemy.run/sql/Postgres"),
69
+ ]),
70
+ );
71
+ const { client, ...drizzleConfig } = config ?? {};
72
+ return yield* PgDrizzle.makeWithDefaults(drizzleConfig as EffectDrizzlePgConfig<TRelations>).pipe(
73
+ Effect.provideServiceEffect(PgClient, SQL.Postgres({ ...client, url: connectionString })),
100
74
  );
75
+ });
package/src/Schema.ts CHANGED
@@ -61,7 +61,7 @@ type Hint =
61
61
  | { type: "rename"; kind: string; from: ReadonlyArray<string>; to: ReadonlyArray<string> }
62
62
  | { type: "create"; kind: string; entity: ReadonlyArray<string> };
63
63
 
64
- type GenerateOptions = {
64
+ type GenerateOptions = SchemaGenerateOptions & {
65
65
  dialect: string;
66
66
  schema: string;
67
67
  out: string;
@@ -161,6 +161,28 @@ class UndecidedSchemaChange extends Data.TaggedError("UndecidedSchemaChange")<{
161
161
  }
162
162
  }
163
163
 
164
+ /**
165
+ * Options passed through to `drizzle-kit generate`, matching the fields of
166
+ * the same name in `drizzle.config.ts`. `dialect`, `schema` and `out` come
167
+ * from {@link SchemaProps}.
168
+ */
169
+ export type SchemaGenerateOptions = {
170
+ /**
171
+ * Separate statements with `--> statement-breakpoint` markers. Set to
172
+ * `false` to match migrations generated with `breakpoints: false`.
173
+ *
174
+ * @default true
175
+ */
176
+ breakpoints?: boolean;
177
+ /**
178
+ * Generate even when migrations from parallel branches conflict, writing
179
+ * a merge migration. Not supported for postgres.
180
+ *
181
+ * @default false
182
+ */
183
+ ignoreConflicts?: boolean;
184
+ };
185
+
164
186
  export type SchemaProps = {
165
187
  /**
166
188
  * Path to the schema module, relative to the current working directory.
@@ -187,6 +209,12 @@ export type SchemaProps = {
187
209
  * @default "postgres"
188
210
  */
189
211
  dialect?: Dialect;
212
+ /**
213
+ * Options passed through to `drizzle-kit generate`.
214
+ *
215
+ * @example { breakpoints: false }
216
+ */
217
+ generate?: SchemaGenerateOptions;
190
218
  };
191
219
 
192
220
  export type Schema = Resource<
@@ -266,8 +294,11 @@ const exitTrap = Semaphore.makeUnsafe(1);
266
294
  const callDrizzleKit = <A>(command: string, run: () => Promise<A>) =>
267
295
  Effect.acquireUseRelease(
268
296
  Effect.sync(() => {
269
- // oxlint-disable-next-line typescript/unbound-method -- saved only to restore `process.exit` verbatim, never called unbound
270
- const trap = { exit: process.exit, exited: false };
297
+ // Saved only to restore `process.exit` verbatim, never called unbound.
298
+ // Read through `Reflect.get`: whether `typescript/unbound-method`
299
+ // flags `process.exit` depends on which `process` typing the linter's
300
+ // program resolves, which unrelated files change.
301
+ const trap = { exit: Reflect.get(process, "exit") as typeof process.exit, exited: false };
271
302
  process.exit = () => {
272
303
  trap.exited = true;
273
304
  throw new Error("drizzle-kit exited");
@@ -470,6 +501,7 @@ export const SchemaProvider = () =>
470
501
  // questions (columns of a renamed table), so ask until none are left.
471
502
  const result = yield* Effect.gen(function* () {
472
503
  const result = yield* drizzleKit.generate({
504
+ ...props.generate,
473
505
  dialect: cliDialect(props),
474
506
  schema: schemaGlob(props),
475
507
  out,
@@ -509,7 +541,7 @@ export const SchemaProvider = () =>
509
541
  effect.pipe(
510
542
  Effect.scoped,
511
543
  Artifacts.cached(
512
- `detectDrift:${cliDialect(props)}:${resolveSchema(props)}:${resolveOut(props)}`,
544
+ `detectDrift:${cliDialect(props)}:${resolveSchema(props)}:${resolveOut(props)}:${JSON.stringify(props.generate ?? {})}`,
513
545
  ),
514
546
  ),
515
547
  );
@@ -524,6 +556,7 @@ export const SchemaProvider = () =>
524
556
  if (changed) {
525
557
  yield* fs.makeDirectory(out, { recursive: true });
526
558
  const result = yield* drizzleKit.generate({
559
+ ...props.generate,
527
560
  dialect: cliDialect(props),
528
561
  schema: schemaGlob(props),
529
562
  out,