diff --git a/.changeset/read-only-bun-sqlite.md b/.changeset/read-only-bun-sqlite.md new file mode 100644 index 00000000000..9037b67ad05 --- /dev/null +++ b/.changeset/read-only-bun-sqlite.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-bun": patch +--- + +Enforce read-only mode when opening Bun SQLite databases. diff --git a/packages/sql/sqlite-bun/src/SqliteClient.ts b/packages/sql/sqlite-bun/src/SqliteClient.ts index 5e7195fb264..745f12a5dab 100644 --- a/packages/sql/sqlite-bun/src/SqliteClient.ts +++ b/packages/sql/sqlite-bun/src/SqliteClient.ts @@ -115,14 +115,15 @@ export const make = ( undefined const makeConnection = Effect.gen(function*() { + const readonly = options.readonly === true const db = new Database(options.filename, { - readonly: options.readonly, - readwrite: options.readwrite ?? true, - create: options.create ?? true + readonly, + readwrite: readonly ? false : options.readwrite ?? true, + create: readonly ? false : options.create ?? true } as any) yield* Effect.addFinalizer(() => Effect.sync(() => db.close())) - if (options.disableWAL !== true) { + if (options.disableWAL !== true && !readonly) { db.run("PRAGMA journal_mode = WAL;") } diff --git a/packages/sql/sqlite-bun/test/Client.test.ts b/packages/sql/sqlite-bun/test/Client.test.ts index fc3285a3604..82259957333 100644 --- a/packages/sql/sqlite-bun/test/Client.test.ts +++ b/packages/sql/sqlite-bun/test/Client.test.ts @@ -1,6 +1,35 @@ -import { describe, it } from "@effect/vitest" +import { assert, describe, it } from "@effect/vitest" import { Effect } from "effect" +import { Reactivity } from "effect/unstable/reactivity" +import { rm } from "node:fs/promises" + +const isBun = "bun" in process.versions describe("Client", () => { it.effect("should work", () => Effect.void) + + it.effect.skipIf(!isBun)("readonly clients reject writes", () => + Effect.gen(function*() { + const { SqliteClient } = yield* Effect.promise(() => import("@effect/sql-sqlite-bun")) + const filename = `/tmp/effect-sqlite-bun-readonly-${crypto.randomUUID()}.db` + yield* Effect.acquireRelease( + Effect.void, + () => Effect.promise(() => rm(filename, { force: true })) + ) + + yield* Effect.scoped( + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename }) + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY)` + }) + ) + + const sql = yield* SqliteClient.make({ filename, readonly: true }) + assert.deepStrictEqual(yield* sql`SELECT * FROM test`, []) + + const error = yield* Effect.flip(sql`INSERT INTO test DEFAULT VALUES`) + assert.strictEqual(error._tag, "SqlError") + assert(error.reason.cause instanceof Error) + assert.match(error.reason.cause.message, /attempt to write a readonly database/i) + }).pipe(Effect.provide(Reactivity.layer))) })