effect icon indicating copy to clipboard operation
effect copied to clipboard

makeWithConfig in `@effect/[email protected]` does't accept a generic schema type.

Open rohovskoi opened this issue 7 months ago • 2 comments

What version of Effect is running?

3.15.0

What steps can reproduce the bug?

  • Install @effect/[email protected]
  • Use @effect/sql-drizzle/Pg
  • Try creating a layer with layerWithConfig
import * as PgDrizzle from '@effect/sql-drizzle/Pg';
import { PgClient } from '@effect/sql-pg';
import { Cause, Config, Console, Layer } from 'effect';
import * as todoSchema from './schemas/todo.js';
import * as authSchema from './schemas/auth.js';

const DbSchema = { ...authSchema, ...todoSchema}

const PgLive = PgClient.layerConfig({
  url: Config.redacted('DATABASE_URL'),
}).pipe(Layer.tapErrorCause((cause) => Console.log(Cause.pretty(cause))));

const DrizzleLive = PgDrizzle.layerWithConfig({
  casing: 'snake_case',
  schema: DbSchema, // ==> gives a type error
}).pipe(Layer.provide(PgLive));

export const DatabaseLive = Layer.mergeAll(PgLive, DrizzleLive);

You will see a type of error in the schema parameter of layerWithConfig.

Error Message:

Type '{ todosTable: PgTableWithColumns<{ name: "todos"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "todos"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; notNull: true; hasDefault: true; ... 6 more ...; generated: undefined; }, {}, {}>; title: PgColumn<...>; complete...' is not assignable to type 'Record<string, never>'.
Property 'todosTable' is incompatible with index signature.
Type 'PgTableWithColumns<{ name: "todos"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "todos"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; notNull: true; hasDefault: true; ... 6 more ...; generated: undefined; }, {}, {}>; title: PgColumn<...>; completed: PgColumn<.....' is not assignable to type 'never'. (ts 2322)

What is the expected behavior?

I would expect makeWithConfig to accept generics just as make. With generics, we would be able to use all Drizzle table schemas as a unified schema.

const DbSchema = { ...authSchema, ...todoSchema}

What do you see instead?

/**
 * Accepts a generic schema
 */
export const make = <TSchema extends Record<string, unknown> = Record<string, never>>(
  config?: Omit<DrizzleConfig<TSchema>, "logger">
): Effect.Effect<PgRemoteDatabase<TSchema>, never, Client.SqlClient> =>
  Effect.gen(function*() {
    const client = yield* Client.SqlClient
    const db = drizzle(yield* makeRemoteCallback, config)
    registerDialect((db as any).dialect, client)
    return db
  })
/**
 * Doesn't accept a generic schema
 */
export const makeWithConfig: (config: DrizzleConfig) => Effect.Effect<PgRemoteDatabase, never, Client.SqlClient> = (
  config
) =>
  Effect.gen(function*() {
    const client = yield* Client.SqlClient
    const db = drizzle(yield* makeRemoteCallback, config)
    registerDialect((db as any).dialect, client)
    return db
  })

Additional information

As layerWithConfig depends on makeWithConfig, it's pretty much unusable given that many use-cases have their schemas defined as in the example above.

rohovskoi avatar May 13 '25 14:05 rohovskoi

Image

Problem is likely the lack of generic on pgRemoteDatabase on the context tag is defaulting the field value to never.

afonsomatos avatar May 16 '25 14:05 afonsomatos

@afonsomatos yeah. Texted about it in Discord as well.

rohovskoi avatar May 23 '25 23:05 rohovskoi

layers cannot be generic so accepting a schema in the base layer would not have any effect, the way to use ORM features like shown in tests is:

import * as SqliteDrizzle from "@effect/sql-drizzle/Sqlite"
import * as D from "drizzle-orm/sqlite-core"
import { Effect } from "effect"

export const users = D.sqliteTable("users", {
  id: D.integer("id").primaryKey(),
  name: D.text("name"),
  snakeCase: D.text("snake_case")
})

export class ORM extends Effect.Service<ORM>()("ORM", {
  effect: SqliteDrizzle.make({ schema: { users } })
}) {}

export const program = Effect.gen(function*() {
  const orm = yield* ORM

  yield* orm.query.users.findMany()
})

You basically need to create your own specific service that identifies the drizzle instance with the schema

mikearnaldi avatar Jul 23 '25 06:07 mikearnaldi