From efcbc153ee9d86b1908a6eb940b9264eddf6a3ad Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 27 Apr 2026 22:27:53 -0400 Subject: [PATCH] test(effect-drizzle-sqlite): cover transaction edge cases --- packages/effect-drizzle-sqlite/src/index.ts | 15 +++- .../effect-drizzle-sqlite/test/sqlite.test.ts | 79 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/packages/effect-drizzle-sqlite/src/index.ts b/packages/effect-drizzle-sqlite/src/index.ts index 9e0be1a489..868dc23274 100644 --- a/packages/effect-drizzle-sqlite/src/index.ts +++ b/packages/effect-drizzle-sqlite/src/index.ts @@ -7,7 +7,7 @@ import { SQLiteInsertBase } from "drizzle-orm/sqlite-core/query-builders/insert" import { SQLiteRelationalQuery, SQLiteSyncRelationalQuery } from "drizzle-orm/sqlite-core/query-builders/_query" import { SQLiteSelectBase } from "drizzle-orm/sqlite-core/query-builders/select" import { SQLiteUpdateBase } from "drizzle-orm/sqlite-core/query-builders/update" -import type { SQLiteTransaction, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" +import type { PreparedQueryConfig, SQLiteSession, SQLiteTransaction, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" import { SQLitePreparedQuery } from "drizzle-orm/sqlite-core/session" import type { DrizzleConfig } from "drizzle-orm/utils" import { Cause, Effect, Exit, Schema } from "effect" @@ -241,3 +241,16 @@ declare module "drizzle-orm/query-promise" { asEffect(): Effect.Effect } } + +declare module "drizzle-orm/sqlite-core/session" { + interface SQLitePreparedQuery extends Effect.Effect { + asEffect(): Effect.Effect + } +} + +declare module "drizzle-orm/sqlite-core/query-builders/count" { + interface SQLiteCountBuilder> + extends Effect.Effect { + asEffect(): Effect.Effect + } +} diff --git a/packages/effect-drizzle-sqlite/test/sqlite.test.ts b/packages/effect-drizzle-sqlite/test/sqlite.test.ts index 2f18329031..6e26bca2ba 100644 --- a/packages/effect-drizzle-sqlite/test/sqlite.test.ts +++ b/packages/effect-drizzle-sqlite/test/sqlite.test.ts @@ -1,6 +1,8 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { Database } from "bun:sqlite" import { eq } from "drizzle-orm" import { relations } from "drizzle-orm/_relations" +import { drizzle as drizzleBun } from "drizzle-orm/bun-sqlite" import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" import { Cause, Effect, Exit } from "effect" import { EffectDrizzleQueryError, make, type EffectSQLiteDatabase } from "../src" @@ -49,6 +51,21 @@ afterEach(() => { }) describe("effect drizzle sqlite", () => { + test("keeps normal Drizzle Bun SQLite clients usable after patching", async () => { + const sqlite = new Database(":memory:") + try { + const normal = drizzleBun({ client: sqlite }) + sqlite.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") + + normal.insert(users).values({ id: 1, name: "Ada" }).run() + + expect(normal.select().from(users).all()).toEqual([{ id: 1, name: "Ada" }]) + expect(await normal.select().from(users)).toEqual([{ id: 1, name: "Ada" }]) + } finally { + sqlite.close() + } + }) + testEffect("makes select/insert/update/delete query builders yieldable Effects", () => Effect.gen(function* () { yield* db.insert(users).values({ id: 1, name: "Ada" }) @@ -141,6 +158,68 @@ describe("effect drizzle sqlite", () => { }), ) + testEffect("supports count builders and prepared queries", () => + Effect.gen(function* () { + yield* db.insert(users).values([ + { id: 1, name: "Ada" }, + { id: 2, name: "Grace" }, + ]) + + expect(yield* db.$count(users)).toBe(2) + + const prepared = db.select().from(users).orderBy(users.id).prepare() + expect(yield* prepared).toEqual([ + { id: 1, name: "Ada" }, + { id: 2, name: "Grace" }, + ]) + }), + ) + + testEffect("nested pipeable transactions commit or roll back with the outer transaction", () => + Effect.gen(function* () { + yield* Effect.gen(function* () { + yield* db.insert(users).values({ id: 1, name: "Ada" }) + yield* Effect.gen(function* () { + yield* db.insert(users).values({ id: 2, name: "Grace" }) + }).pipe(db.withTransaction) + }).pipe(db.withTransaction) + + expect(yield* db.select().from(users).orderBy(users.id)).toEqual([ + { id: 1, name: "Ada" }, + { id: 2, name: "Grace" }, + ]) + + const exit = yield* Effect.gen(function* () { + yield* db.insert(users).values({ id: 3, name: "Katherine" }) + yield* Effect.gen(function* () { + yield* db.insert(users).values({ id: 4, name: "Dorothy" }) + return yield* Effect.fail("inner rollback") + }).pipe(db.withTransaction) + }).pipe(db.withTransaction, Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + expect(yield* db.select().from(users).orderBy(users.id)).toEqual([ + { id: 1, name: "Ada" }, + { id: 2, name: "Grace" }, + ]) + }), + ) + + testEffect("defects inside transactions roll back and stay defects", () => + Effect.gen(function* () { + const exit = yield* Effect.gen(function* () { + yield* db.insert(users).values({ id: 1, name: "Ada" }) + return yield* Effect.die("boom") + }).pipe(db.withTransaction, Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + expect(exit.cause.reasons.some(Cause.isDieReason)).toBe(true) + } + expect(yield* db.select().from(users)).toEqual([]) + }), + ) + testEffect("wraps query failures with query text and parameters", () => Effect.gen(function* () { const exit = yield* Effect.exit(db.insert(posts).values({ id: 1, user_id: 404, title: "Missing" }))