| 1 | import type { DurableObjectNamespace, DurableObjectState } from "@cloudflare/workers-types" |
| 2 | import * as SqliteClient from "@effect/sql-sqlite-do/SqliteClient" |
| 3 | import { Effect, Exit, Fiber, Scheduler } from "effect" |
| 4 | import * as SqlClient from "effect/sql/SqlClient" |
| 5 | |
| 6 | export class TransactionObject { |
| 7 | constructor(private readonly state: DurableObjectState) {} |
| 8 | |
| 9 | async fetch(request: Request) { |
| 10 | const mode = new URL(request.url).pathname |
| 11 | const program = Effect.gen(function*() { |
| 12 | const sql = yield* SqlClient.SqlClient |
| 13 | yield* sql`CREATE TABLE writes (id INTEGER PRIMARY KEY)` |
| 14 | // Queue an outside timer before the transaction closes the input gate. |
| 15 | const outside = yield* Effect.forkScoped(sql`SELECT count(*) AS n FROM writes`) |
| 16 | // Exhaust the operation budget using synchronous SQL, not explicit yieldNow. |
| 17 | const writes = Effect.forEach( |
| 18 | Array.from({ length: 64 }, (_, id) => id), |
| 19 | (id) => sql`INSERT INTO writes VALUES (${id})`, |
| 20 | { discard: true } |
| 21 | ).pipe(Effect.provideService(Scheduler.MaxOpsBeforeYield, 32)) |
| 22 | const body = mode === "/nested" |
| 23 | ? sql.withTransaction(writes) |
| 24 | : mode === "/rollback" |
| 25 | ? Effect.andThen(writes, Effect.fail("rollback")) |
| 26 | : writes |
| 27 | const exit = yield* Effect.exit(sql.withTransaction(body)) |
| 28 | const outsideRows = yield* Fiber.join(outside) |
| 29 | const rows = yield* sql`SELECT count(*) AS n FROM writes` |
| 30 | return { success: Exit.isSuccess(exit), outsideRows, rows } |
| 31 | }).pipe(Effect.scoped, Effect.provide(SqliteClient.layer({ storage: this.state.storage }))) |
| 32 | return Response.json(await Effect.runPromise(program)) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | export default { |
| 37 | fetch(request: Request, env: { TEST: DurableObjectNamespace }) { |
| 38 | return env.TEST.get(env.TEST.idFromName(new URL(request.url).pathname)).fetch(request.url) |
| 39 | } |
| 40 | } |