sqlite-workerd.test.ts 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { describe, expect, test } from "bun:test"
  2. import { Effect, Layer } from "effect"
  3. import { SqlClient } from "effect/unstable/sql"
  4. import { SqlError } from "effect/unstable/sql/SqlError"
  5. import { sqliteLayer } from "@opencode-ai/core/database/sqlite.workerd"
  6. import type { DurableObjectStorage } from "@opencode-ai/core/database/sqlite.workerd"
  7. import { makeDurableObjectStorage } from "./fixture/durable-object-storage"
  8. import { tempGlobalLayer } from "./fixture/global"
  9. const run = <A, E>(storage: DurableObjectStorage, effect: Effect.Effect<A, E, SqlClient.SqlClient>) =>
  10. Effect.runPromise(effect.pipe(Effect.provide(sqliteLayer({ storage })), Effect.scoped))
  11. describe("sqlite.workerd", () => {
  12. test("executes statements with bindings and maps rows to records", async () => {
  13. const rows = await run(
  14. makeDurableObjectStorage(),
  15. Effect.gen(function* () {
  16. const sql = yield* SqlClient.SqlClient
  17. yield* sql`CREATE TABLE item (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`
  18. yield* sql`INSERT INTO item (id, name) VALUES (${1}, ${"one"}), (${2}, ${"two"})`
  19. return yield* sql<{ id: number; name: string }>`SELECT id, name FROM item ORDER BY id`
  20. }),
  21. )
  22. expect(rows).toEqual([
  23. { id: 1, name: "one" },
  24. { id: 2, name: "two" },
  25. ])
  26. })
  27. test("normalizes ArrayBuffer blob values to Uint8Array", async () => {
  28. const rows = await run(
  29. makeDurableObjectStorage(),
  30. Effect.gen(function* () {
  31. const sql = yield* SqlClient.SqlClient
  32. yield* sql`CREATE TABLE blob (data BLOB NOT NULL)`
  33. yield* sql`INSERT INTO blob (data) VALUES (${new Uint8Array([1, 2, 3])})`
  34. return yield* sql<{ data: Uint8Array }>`SELECT data FROM blob`
  35. }),
  36. )
  37. expect(rows[0].data).toBeInstanceOf(Uint8Array)
  38. expect(Array.from(rows[0].data)).toEqual([1, 2, 3])
  39. })
  40. test("withTransaction commits on success and rolls back on failure", async () => {
  41. const storage = makeDurableObjectStorage()
  42. const count = await run(
  43. storage,
  44. Effect.gen(function* () {
  45. const sql = yield* SqlClient.SqlClient
  46. yield* sql`CREATE TABLE t (value TEXT NOT NULL)`
  47. yield* sql.withTransaction(sql`INSERT INTO t (value) VALUES (${"kept"})`)
  48. yield* sql
  49. .withTransaction(
  50. Effect.gen(function* () {
  51. yield* sql`INSERT INTO t (value) VALUES (${"discarded"})`
  52. return yield* Effect.fail("rollback")
  53. }),
  54. )
  55. .pipe(Effect.ignore)
  56. return yield* sql<{ count: number }>`SELECT count(*) AS count FROM t`
  57. }),
  58. )
  59. expect(count[0].count).toBe(1)
  60. })
  61. test("nested withTransaction fails with SqlError", async () => {
  62. const error = await run(
  63. makeDurableObjectStorage(),
  64. Effect.gen(function* () {
  65. const sql = yield* SqlClient.SqlClient
  66. yield* sql`CREATE TABLE t (value TEXT NOT NULL)`
  67. return yield* sql
  68. .withTransaction(sql.withTransaction(sql`INSERT INTO t (value) VALUES (${"nested"})`))
  69. .pipe(Effect.flip)
  70. }),
  71. )
  72. expect(error).toBeInstanceOf(SqlError)
  73. })
  74. test("boots the full database layer with migrations over injected storage", async () => {
  75. const storage = makeDurableObjectStorage()
  76. const core = await import("@opencode-ai/core/database/database")
  77. await Effect.runPromise(
  78. Effect.scoped(
  79. Layer.build(
  80. core.Database.layerFromClient.pipe(Layer.provide(sqliteLayer({ storage })), Layer.provide(tempGlobalLayer)),
  81. ),
  82. ),
  83. )
  84. const names = storage.sql
  85. .exec("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
  86. .toArray()
  87. .map((row) => row.name)
  88. expect(names).toContain("migration")
  89. expect(names).toContain("session_v2")
  90. })
  91. })