import 'server-only' import * as PgDrizzle from '@effect/sql-drizzle/Pg' import { eq } from 'drizzle-orm' import { Context, Effect, Layer, Schema } from 'effect' import { User } from '@workspace/database/drizzle/schema' import { selectUserSchema } from '@workspace/database/effect/user-schemas' import type { InsertUser } from '@workspace/database/effect/user-types' export class DatabaseError extends Schema.TaggedError()('DatabaseError', { cause: Schema.Unknown, }) {} export class UserNotFound extends Schema.TaggedError()('UserNotFound', { id: Schema.String, }) {} const makeUsersService = Effect.gen(function* () { const drizzle = yield* PgDrizzle.PgDrizzle return { getAllUsers: drizzle .select() .from(User) .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(users => Effect.all(users.map(u => Schema.decodeUnknown(selectUserSchema)(u))) ) ), getUserById: (id: string) => drizzle .select() .from(User) .where(eq(User.id, id)) .limit(1) .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(result => result[0] ? Effect.succeed(result[0]) : Effect.fail(new UserNotFound({ id })) ), Effect.flatMap(u => Schema.decodeUnknown(selectUserSchema)(u)) ), createUser: (data: InsertUser) => drizzle .insert(User) .values(data) .returning() .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(result => result[0] ? Effect.succeed(result[0]) : Effect.fail(new DatabaseError({ cause: 'No user returned after insert' })) ), Effect.flatMap(u => Schema.decodeUnknown(selectUserSchema)(u)) ), updateUser: (id: string, data: Partial) => Effect.gen(function* () { const existingUser = yield* drizzle .select() .from(User) .where(eq(User.id, id)) .limit(1) .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(result => result[0] ? Effect.succeed(result[0]) : Effect.fail(new UserNotFound({ id })) ) ) const [updatedUser] = yield* drizzle .update(User) .set({ ...data, updatedAt: new Date() }) .where(eq(User.id, id)) .returning() .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(result => result.length > 0 ? Effect.succeed(result) : Effect.fail(new UserNotFound({ id })) ) ) return yield* Schema.decodeUnknown(selectUserSchema)(updatedUser) }), deleteUser: (id: string) => Effect.gen(function* () { const existingUser = yield* drizzle .select() .from(User) .where(eq(User.id, id)) .limit(1) .pipe( Effect.mapError(error => new DatabaseError({ cause: error })), Effect.flatMap(result => result[0] ? Effect.succeed(result[0]) : Effect.fail(new UserNotFound({ id })) ) ) yield* drizzle .delete(User) .where(eq(User.id, id)) .pipe(Effect.mapError(error => new DatabaseError({ cause: error }))) return yield* Schema.decodeUnknown(selectUserSchema)(existingUser) }), } }) export class UsersService extends Context.Tag('UsersService')< UsersService, Effect.Effect.Success >() { static readonly Live = Layer.effect(UsersService, makeUsersService) }