1import type { DurableObjectNamespace, DurableObjectState } from "@cloudflare/workers-types"
2import * as SqliteClient from "@effect/sql-sqlite-do/SqliteClient"
3import { Effect, Exit, Fiber, Scheduler } from "effect"
4import * as SqlClient from "effect/sql/SqlClient"
5
6export 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
36export 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}