(options: {
readonly transactionService: Context.Key<I, readonly [conn: S, counter: number]>
readonly spanAttributes: ReadonlyArray<readonly [string, unknown]>
readonly acquireConnection: Effect.Effect<readonly [Scope.Closeable | undefined, S], SqlError>
readonly begin: (conn: NoInfer<S>) => Effect.Effect<void, SqlError>
readonly savepoint: (conn: NoInfer<S>, id: number) => Effect.Effect<void, SqlError>
readonly commit: (conn: NoInfer<S>) => Effect.Effect<void, SqlError>
readonly rollback: (conn: NoInfer<S>) => Effect.Effect<void, SqlError>
readonly rollbackSavepoint: (conn: NoInfer<S>, id: number) => Effect.Effect<void, SqlError>
})
| 222 | * @since 4.0.0 |
| 223 | */ |
| 224 | export const makeWithTransaction = <I, S>(options: { |
| 225 | readonly transactionService: Context.Key<I, readonly [conn: S, counter: number]> |
| 226 | readonly spanAttributes: ReadonlyArray<readonly [string, unknown]> |
| 227 | readonly acquireConnection: Effect.Effect<readonly [Scope.Closeable | undefined, S], SqlError> |
| 228 | readonly begin: (conn: NoInfer<S>) => Effect.Effect<void, SqlError> |
| 229 | readonly savepoint: (conn: NoInfer<S>, id: number) => Effect.Effect<void, SqlError> |
| 230 | readonly commit: (conn: NoInfer<S>) => Effect.Effect<void, SqlError> |
| 231 | readonly rollback: (conn: NoInfer<S>) => Effect.Effect<void, SqlError> |
| 232 | readonly rollbackSavepoint: (conn: NoInfer<S>, id: number) => Effect.Effect<void, SqlError> |
| 233 | }) => { |
| 234 | const transactionSemaphore = Context.Service<Semaphore.Semaphore>( |
| 235 | `effect/sql/SqlClient/TransactionSemaphore/${transactionSemaphoreIdCounter++}` |
| 236 | ) |
| 237 | return <R, E, A>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E | SqlError, R> => |
| 238 | Effect.uninterruptibleMask((restore) => |
| 239 | Effect.useSpan( |
| 240 | "sql.transaction", |
| 241 | { kind: "client" }, |
| 242 | (span) => |
| 243 | Effect.withFiber<A, E | SqlError, R>((fiber) => { |
| 244 | for (const [key, value] of options.spanAttributes) { |
| 245 | span.attribute(key, value) |
| 246 | } |
| 247 | const services = fiber.context |
| 248 | const clock = fiber.getRef(Clock) |
| 249 | const connOption = Context.getOption(services, options.transactionService) |
| 250 | const conn = connOption._tag === "Some" |
| 251 | ? Effect.succeed([undefined, connOption.value[0]] as const) |
| 252 | : options.acquireConnection |
| 253 | const id = connOption._tag === "Some" ? connOption.value[1] + 1 : 0 |
| 254 | const transaction = Effect.flatMap( |
| 255 | conn, |
| 256 | ( |
| 257 | [scope, conn] |
| 258 | ) => |
| 259 | (id === 0 ? options.begin(conn) : options.savepoint(conn, id)).pipe( |
| 260 | Effect.flatMap(() => |
| 261 | Effect.onExitPrimitive( |
| 262 | Effect.provideContext( |
| 263 | restore(effect), |
| 264 | services.pipe( |
| 265 | Context.add(options.transactionService, [conn, id]), |
| 266 | Context.add(transactionSemaphore, Semaphore.makeUnsafe(1)), |
| 267 | Context.add(Tracer.ParentSpan, span) |
| 268 | ) |
| 269 | ), |
| 270 | (exit) => { |
| 271 | let effect: Effect.Effect<void> |
| 272 | if (Exit.isSuccess(exit)) { |
| 273 | if (id === 0) { |
| 274 | span.event("db.transaction.commit", clock.currentTimeNanosUnsafe()) |
| 275 | effect = Effect.orDie(options.commit(conn)) |
| 276 | } else { |
| 277 | span.event("db.transaction.savepoint", clock.currentTimeNanosUnsafe()) |
| 278 | effect = Effect.void |
| 279 | } |
| 280 | } else { |
| 281 | span.event("db.transaction.rollback", clock.currentTimeNanosUnsafe()) |
no test coverage detected