import { assert, describe, it } from "@effect/vitest" import { Cause, Effect, Fiber, Option, Result, TxQueue } from "effect" describe("TxQueue", () => { describe("interfaces", () => { it.effect("TxEnqueue provides write-only interface", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Can assign to TxEnqueue interface const enqueue: TxQueue.TxEnqueue = queue // TxEnqueue type guard should work assert.strictEqual(TxQueue.isTxEnqueue(enqueue), true) // Can use enqueue operations const offered = yield* TxQueue.offer(queue, 42) assert.strictEqual(offered, true) const rejected = yield* TxQueue.offerAll(queue, [1, 2, 3]) assert.deepStrictEqual(rejected, []) }))) it.effect("TxDequeue provides read-only interface", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) // Can assign to TxDequeue interface const dequeue: TxQueue.TxDequeue = queue // TxDequeue type guard should work assert.strictEqual(TxQueue.isTxDequeue(dequeue), true) // Can use dequeue operations const item = yield* TxQueue.take(queue) assert.strictEqual(item, 1) const maybe = yield* TxQueue.poll(queue) assert.deepStrictEqual(maybe, Option.some(2)) }))) it.effect("TxQueue provides both enqueue and dequeue operations", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // TxQueue should be both TxEnqueue and TxDequeue assert.strictEqual(TxQueue.isTxQueue(queue), true) assert.strictEqual(TxQueue.isTxEnqueue(queue), true) assert.strictEqual(TxQueue.isTxDequeue(queue), true) // Can use both enqueue and dequeue operations yield* TxQueue.offer(queue, 42) const item = yield* TxQueue.take(queue) assert.strictEqual(item, 42) }))) }) describe("interface segregation", () => { it.effect("TxEnqueue interface enforces write-only operations", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const enqueue: TxQueue.TxEnqueue = queue // Enqueue operations should work const accepted = yield* TxQueue.offer(enqueue, 42) assert.strictEqual(accepted, true) const rejected = yield* TxQueue.offerAll(enqueue, [1, 2, 3]) assert.deepStrictEqual(rejected, []) // State management operations should work const result = yield* TxQueue.failCause(enqueue, Cause.interrupt()) assert.strictEqual(result, true) const endResult = yield* TxQueue.end(enqueue) assert.strictEqual(endResult, false) // Already done }))) it.effect("TxDequeue interface enforces read-only operations", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) const dequeue: TxQueue.TxDequeue = queue // Dequeue operations should work const item = yield* TxQueue.take(dequeue) assert.strictEqual(item, 1) const maybe = yield* TxQueue.poll(dequeue) assert.deepStrictEqual(maybe, Option.some(2)) const peek = yield* TxQueue.peek(dequeue) assert.strictEqual(peek, 3) // Should be 3 since we took 1 and poll took 2 // State inspection operations should work const size = yield* TxQueue.size(dequeue) assert.strictEqual(size, 1) const isEmpty = yield* TxQueue.isEmpty(dequeue) assert.strictEqual(isEmpty, false) const isOpen = yield* TxQueue.isOpen(dequeue) assert.strictEqual(isOpen, true) }))) it.effect("Interface assignments maintain type safety", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Full queue can be assigned to either interface const enqueue: TxQueue.TxEnqueue = queue const dequeue: TxQueue.TxDequeue = queue const fullQueue: TxQueue.TxQueue = queue // Verify all interfaces maintain functionality yield* TxQueue.offer(enqueue, 1) yield* TxQueue.offer(fullQueue, 2) const item1 = yield* TxQueue.take(dequeue) const item2 = yield* TxQueue.take(fullQueue) assert.strictEqual(item1, 1) assert.strictEqual(item2, 2) // State queries work on all interfaces const size1 = yield* TxQueue.size(dequeue) const size2 = yield* TxQueue.size(fullQueue) assert.strictEqual(size1, size2) }))) it.effect("Interface type guards work correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const enqueue: TxQueue.TxEnqueue = queue const dequeue: TxQueue.TxDequeue = queue // All type guards should return true for interfaces implemented by TxQueue assert.strictEqual(TxQueue.isTxQueue(queue), true) assert.strictEqual(TxQueue.isTxEnqueue(enqueue), true) assert.strictEqual(TxQueue.isTxDequeue(dequeue), true) // Cross-interface type guards should work since TxQueue implements both assert.strictEqual(TxQueue.isTxEnqueue(queue), true) assert.strictEqual(TxQueue.isTxDequeue(queue), true) assert.strictEqual(TxQueue.isTxQueue(enqueue), true) assert.strictEqual(TxQueue.isTxQueue(dequeue), true) }))) }) describe("constructors", () => { it.effect("bounded creates queue with specified capacity", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const size = yield* TxQueue.size(queue) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(size, 0) assert.strictEqual(isEmpty, true) }))) it.effect("unbounded creates queue with unlimited capacity", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.unbounded() const size = yield* TxQueue.size(queue) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(size, 0) assert.strictEqual(isEmpty, true) }))) it.effect("dropping creates queue with dropping strategy", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.dropping(2) const size = yield* TxQueue.size(queue) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(size, 0) assert.strictEqual(isEmpty, true) }))) it.effect("sliding creates queue with sliding strategy", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.sliding(2) const size = yield* TxQueue.size(queue) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(size, 0) assert.strictEqual(isEmpty, true) }))) }) describe("basic operations", () => { it.effect("offer and take work correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const offered = yield* TxQueue.offer(queue, "hello") assert.strictEqual(offered, true) const item = yield* TxQueue.take(queue) assert.strictEqual(item, "hello") }))) it.effect("poll returns Option.none for empty queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const maybe = yield* TxQueue.poll(queue) assert.deepStrictEqual(maybe, Option.none()) }))) it.effect("poll returns Option.some for non-empty queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offer(queue, 42) const maybe = yield* TxQueue.poll(queue) assert.deepStrictEqual(maybe, Option.some(42)) }))) it.effect("offerAll works correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const rejected = yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) assert.deepStrictEqual(rejected, []) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 5) }))) it.effect("takeAll works correctly with new signature", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) const items = yield* TxQueue.takeAll(queue) assert.deepStrictEqual(items, [1, 2, 3, 4, 5]) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 0) }))) it.effect("takeAll with interrupted queue returns done flag", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2]) yield* TxQueue.interrupt(queue) // Interrupt the queue // takeAll should return all items const items = yield* TxQueue.takeAll(queue) assert.deepStrictEqual(items, [1, 2]) }))) it.effect("takeAll with failed queue propagates error", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2]) yield* TxQueue.fail(queue, "queue failed") // takeAll should fail with the queue error const result = yield* Effect.flip(TxQueue.takeAll(queue)) assert.strictEqual(result, "queue failed") }))) it.effect("takeN works correctly with new signature", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) const items = yield* TxQueue.takeN(queue, 3) assert.deepStrictEqual(items, [1, 2, 3]) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 2) }))) it.effect("takeN with queue completion gets available items", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2]) yield* TxQueue.interrupt(queue) // Interrupt the queue // Take more than available - should get all items const items = yield* TxQueue.takeN(queue, 5) assert.deepStrictEqual(items, [1, 2]) }))) it.effect("takeN with failed queue propagates error", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) yield* TxQueue.fail(queue, "queue failed") // takeN should fail with the queue error const result = yield* Effect.flip(TxQueue.takeN(queue, 2)) assert.strictEqual(result, "queue failed") }))) it.effect("takeN with capacity constraint takes available items", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(3) // Capacity is 3 yield* TxQueue.offerAll(queue, [1, 2, 3]) // Fill to capacity // Request more than capacity - should take all available immediately const items = yield* TxQueue.takeN(queue, 10) assert.deepStrictEqual(items, [1, 2, 3]) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 0) }))) it.effect("takeAll() on empty closed queue gets interrupted", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // Empty queue becomes Done immediately // takeAll should get interrupted since queue is Done const result = yield* Effect.exit(TxQueue.takeAll(queue)) assert.strictEqual(result._tag, "Failure") if (result._tag === "Failure") { assert.strictEqual(Cause.hasInterruptsOnly(result.cause), true) } }))) it.effect("takeAll() waits when empty then returns items when available", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Start takeAll in a fiber - it should wait because queue is empty const fiber = yield* Effect.forkChild(TxQueue.takeAll(queue)) // Add items to the queue yield* TxQueue.offerAll(queue, [1, 2, 3]) // Now takeAll should complete and return the items const items = yield* Fiber.join(fiber) assert.deepStrictEqual(items, [1, 2, 3]) }))) it.effect("takeAll() during state transition handles race condition", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2]) // Interrupt queue to put it in Closing state yield* TxQueue.interrupt(queue) // takeAll should get items and transition queue to Done const items = yield* TxQueue.takeAll(queue) assert.deepStrictEqual(items, [1, 2]) // Verify queue is now Done const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) it.effect("takeN(0) returns empty array", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) const items = yield* TxQueue.takeN(queue, 0) assert.deepStrictEqual(items, []) // Queue should still have all items const size = yield* TxQueue.size(queue) assert.strictEqual(size, 3) }))) it.effect("takeN() with closing queue returns available items", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2]) yield* TxQueue.interrupt(queue) // Put in Closing state // Request more than available - should get what's available and transition to Done const items = yield* TxQueue.takeN(queue, 5) assert.deepStrictEqual(items, [1, 2]) // Queue should now be Done const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) it.effect("takeN() signature validation - returns Array not [Array, boolean]", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) const items = yield* TxQueue.takeN(queue, 2) // Verify return type is Array, not [Array, boolean] assert.strictEqual(Array.isArray(items), true) assert.deepStrictEqual(items, [1, 2]) // Verify it's not a tuple - should not have boolean as second element assert.strictEqual(typeof items[2], "undefined") }))) it.effect("takeBetween() basic functionality", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7, 8]) // Take between 2 and 5 items const batch1 = yield* TxQueue.takeBetween(queue, 2, 5) assert.strictEqual(batch1.length, 5) assert.deepStrictEqual(batch1, [1, 2, 3, 4, 5]) // Take between 1 and 10 items (but only 3 remain) const batch2 = yield* TxQueue.takeBetween(queue, 1, 10) assert.strictEqual(batch2.length, 3) assert.deepStrictEqual(batch2, [6, 7, 8]) // Verify queue is empty const empty = yield* TxQueue.isEmpty(queue) assert.strictEqual(empty, true) }))) it.effect("takeBetween() takes up to maximum when available", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7]) // Take between 3 and 5 items - should take exactly 5 (the maximum) const items = yield* TxQueue.takeBetween(queue, 3, 5) assert.strictEqual(items.length, 5) assert.deepStrictEqual(items, [1, 2, 3, 4, 5]) // Remaining items should still be in queue const remaining = yield* TxQueue.takeAll(queue) assert.deepStrictEqual(remaining, [6, 7]) }))) it.effect("takeBetween() with invalid parameters", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) // Invalid parameters should return empty array const result1 = yield* TxQueue.takeBetween(queue, 0, 5) assert.deepStrictEqual(result1, []) const result2 = yield* TxQueue.takeBetween(queue, -1, 5) assert.deepStrictEqual(result2, []) const result3 = yield* TxQueue.takeBetween(queue, 5, 2) // min > max assert.deepStrictEqual(result3, []) // Queue should still have all items const size = yield* TxQueue.size(queue) assert.strictEqual(size, 3) }))) it.effect("takeBetween() with failed queue propagates error", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.fail(queue, "test error") const result = yield* Effect.exit(TxQueue.takeBetween(queue, 1, 5)) assert.strictEqual(result._tag, "Failure") if (result._tag === "Failure") { const error = Cause.findError(result.cause) assert.ok(Result.isSuccess(error)) assert.strictEqual(error.success, "test error") } }))) it.effect("takeBetween() with closing queue returns available items", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2]) // Start closing process yield* TxQueue.interrupt(queue) // Should return available items even if less than minimum const items = yield* TxQueue.takeBetween(queue, 3, 5) assert.deepStrictEqual(items, [1, 2]) // Queue should be done now const done = yield* TxQueue.isDone(queue) assert.strictEqual(done, true) }))) it.effect("takeBetween() exact minimum and maximum", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) // Take exactly 3 items (min=max=3) const items = yield* TxQueue.takeBetween(queue, 3, 3) assert.strictEqual(items.length, 3) assert.deepStrictEqual(items, [1, 2, 3]) const empty = yield* TxQueue.isEmpty(queue) assert.strictEqual(empty, true) }))) it.effect("takeBetween() returns Array not tuple", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3, 4]) const items = yield* TxQueue.takeBetween(queue, 2, 3) // Verify return type is Array, not [Array, boolean] assert.strictEqual(Array.isArray(items), true) assert.deepStrictEqual(items, [1, 2, 3]) // Verify it's not a tuple - should not have boolean as extra element assert.strictEqual(typeof items[3], "undefined") }))) it.effect("peek works correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offer(queue, 42) const item = yield* TxQueue.peek(queue) assert.strictEqual(item, 42) // Item should still be in queue const size = yield* TxQueue.size(queue) assert.strictEqual(size, 1) }))) }) describe("queue state", () => { it.effect("size returns correct count", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 3) }))) it.effect("isEmpty works correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const empty1 = yield* TxQueue.isEmpty(queue) assert.strictEqual(empty1, true) yield* TxQueue.offer(queue, 1) const empty2 = yield* TxQueue.isEmpty(queue) assert.strictEqual(empty2, false) }))) it.effect("isFull works correctly for bounded queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(2) const full1 = yield* TxQueue.isFull(queue) assert.strictEqual(full1, false) yield* TxQueue.offerAll(queue, [1, 2]) const full2 = yield* TxQueue.isFull(queue) assert.strictEqual(full2, true) }))) it.effect("isFull returns false for unbounded queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.unbounded() yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) const full = yield* TxQueue.isFull(queue) assert.strictEqual(full, false) }))) }) describe("clear", () => { it.effect("clear removes all items from queue and returns them", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) const sizeBefore = yield* TxQueue.size(queue) assert.strictEqual(sizeBefore, 5) const cleared = yield* TxQueue.clear(queue) assert.deepStrictEqual(cleared, [1, 2, 3, 4, 5]) const sizeAfter = yield* TxQueue.size(queue) assert.strictEqual(sizeAfter, 0) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(isEmpty, true) }))) it.effect("clear does not affect queue state", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) const cleared = yield* TxQueue.clear(queue) assert.deepStrictEqual(cleared, [1, 2, 3]) // Queue should still be open const isOpen = yield* TxQueue.isOpen(queue) assert.strictEqual(isOpen, true) // Should be able to offer new items const offered = yield* TxQueue.offer(queue, 42) assert.strictEqual(offered, true) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 1) }))) it.effect("clear works on empty queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const cleared = yield* TxQueue.clear(queue) assert.deepStrictEqual(cleared, []) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 0) const isEmpty = yield* TxQueue.isEmpty(queue) assert.strictEqual(isEmpty, true) }))) it.effect("clear works on queue ended with Done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offerAll(queue, [1, 2, 3]) yield* TxQueue.end(queue) // Take all items to move from Closing to Done state yield* TxQueue.takeAll(queue) // Verify it's done before clearing const isDoneBefore = yield* TxQueue.isDone(queue) assert.strictEqual(isDoneBefore, true) // clear() returns empty array for halt causes (like Done) const cleared = yield* TxQueue.clear(queue) assert.deepStrictEqual(cleared, []) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 0) // Should still be done after clearing const isDoneAfter = yield* TxQueue.isDone(queue) assert.strictEqual(isDoneAfter, true) }))) }) describe("shutdown", () => { it.effect("shutdown and isShutdown work correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) const isShutdown1 = yield* TxQueue.isShutdown(queue) assert.strictEqual(isShutdown1, false) yield* TxQueue.shutdown(queue) const isShutdown2 = yield* TxQueue.isShutdown(queue) assert.strictEqual(isShutdown2, true) }))) it.effect("offer fails after shutdown", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.shutdown(queue) const offered = yield* TxQueue.offer(queue, 42) assert.strictEqual(offered, false) }))) it.effect("poll returns none after shutdown", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(10) yield* TxQueue.offer(queue, 42) yield* TxQueue.shutdown(queue) const maybe = yield* TxQueue.poll(queue) assert.deepStrictEqual(maybe, Option.none()) }))) }) describe("dropping strategy", () => { it.effect("dropping queue rejects items when full", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.dropping(2) // Fill to capacity const accepted1 = yield* TxQueue.offer(queue, 1) const accepted2 = yield* TxQueue.offer(queue, 2) assert.strictEqual(accepted1, true) assert.strictEqual(accepted2, true) // This should be dropped const accepted3 = yield* TxQueue.offer(queue, 3) assert.strictEqual(accepted3, false) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 2) }))) }) describe("sliding strategy", () => { it.effect("sliding queue evicts old items when full", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.sliding(2) // Fill to capacity yield* TxQueue.offer(queue, 1) yield* TxQueue.offer(queue, 2) // This should evict item 1 const accepted = yield* TxQueue.offer(queue, 3) assert.strictEqual(accepted, true) const size = yield* TxQueue.size(queue) assert.strictEqual(size, 2) // First item should be 2 (item 1 was evicted) const item = yield* TxQueue.take(queue) assert.strictEqual(item, 2) }))) }) describe("E-channel and exit signaling", () => { describe("type ergonomics", () => { it.effect("default E parameter provides clean typing", () => Effect.tx(Effect.gen(function*() { // Clean, concise typing with default E = never const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 42) const item = yield* TxQueue.take(queue) // Effect assert.strictEqual(item, 42) }))) it.effect("explicit E parameter works correctly", () => Effect.tx(Effect.gen(function*() { // Explicit error channel typing const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 42) const item = yield* TxQueue.take(queue) // Effect (no errors when queue is open) assert.strictEqual(item, 42) }))) }) describe("state transitions", () => { it.effect("queue starts in Open state", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const isOpen = yield* TxQueue.isOpen(queue) const isClosing = yield* TxQueue.isClosing(queue) const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isOpen, true) assert.strictEqual(isClosing, false) assert.strictEqual(isDone, false) }))) it.effect("interrupt() transitions Open → Done when empty", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const result = yield* TxQueue.interrupt(queue) assert.strictEqual(result, true) const isOpen = yield* TxQueue.isOpen(queue) const isClosing = yield* TxQueue.isClosing(queue) const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isOpen, false) assert.strictEqual(isClosing, false) assert.strictEqual(isDone, true) }))) it.effect("interrupt() transitions Open → Closing → Done when not empty", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) const result = yield* TxQueue.interrupt(queue) assert.strictEqual(result, true) // Should be in Closing state const isOpen1 = yield* TxQueue.isOpen(queue) const isClosing1 = yield* TxQueue.isClosing(queue) const isDone1 = yield* TxQueue.isDone(queue) assert.strictEqual(isOpen1, false) assert.strictEqual(isClosing1, true) assert.strictEqual(isDone1, false) // Take all items to trigger Closing → Done yield* TxQueue.takeAll(queue) // Should now be Done const isOpen2 = yield* TxQueue.isOpen(queue) const isClosing2 = yield* TxQueue.isClosing(queue) const isDone2 = yield* TxQueue.isDone(queue) assert.strictEqual(isOpen2, false) assert.strictEqual(isClosing2, false) assert.strictEqual(isDone2, true) }))) it.effect("fail() transitions directly to Done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 42) const result = yield* TxQueue.fail(queue, "test error") assert.strictEqual(result, true) const isOpen = yield* TxQueue.isOpen(queue) const isClosing = yield* TxQueue.isClosing(queue) const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isOpen, false) assert.strictEqual(isClosing, false) assert.strictEqual(isDone, true) }))) it.effect("done() with custom cause works correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const customCause = Cause.interrupt() const result = yield* TxQueue.failCause(queue, customCause) assert.strictEqual(result, true) const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) }) describe("completion operations", () => { it.effect("interrupt() completes queue successfully", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const interruptResult = yield* TxQueue.interrupt(queue) assert.strictEqual(interruptResult, true) // awaitCompletion should succeed yield* TxQueue.awaitCompletion(queue) }))) it.effect("fail() completes queue with error", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const failResult = yield* TxQueue.fail(queue, "test error") assert.strictEqual(failResult, true) // awaitCompletion should succeed once queue is done (not propagate the error) yield* TxQueue.awaitCompletion(queue) }))) it.effect("multiple interrupt() calls return false after first", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const result1 = yield* TxQueue.interrupt(queue) const result2 = yield* TxQueue.interrupt(queue) assert.strictEqual(result1, true) assert.strictEqual(result2, false) }))) it.effect("operations on closed queue fail appropriately", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // Offer should return false const offerResult = yield* TxQueue.offer(queue, 42) assert.strictEqual(offerResult, false) // Poll should return none const pollResult = yield* TxQueue.poll(queue) assert.deepStrictEqual(pollResult, Option.none()) }))) }) describe("awaitCompletion operation", () => { it.effect("awaitCompletion succeeds when queue is interrupted", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // Should succeed immediately when queue is already interrupted yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion succeeds when queue fails", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.fail(queue, "connection lost") // Should succeed immediately since queue is done (errors are not propagated) yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion on already completed queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // Should immediately succeed yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion on already failed queue", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.fail(queue, "already failed") // Should immediately succeed since queue is done yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion with custom Cause.die() succeeds", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const defect = new Error("system failure") const defectCause = Cause.die(defect) yield* TxQueue.failCause(queue, defectCause) // awaitCompletion should succeed since queue is done (defects are not propagated) yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion with custom interrupt cause succeeds", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const customInterrupt = Cause.interrupt(12345) yield* TxQueue.failCause(queue, customInterrupt) // awaitCompletion should succeed with interrupt-only causes yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion with fail cause succeeds", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Complete queue with failure cause yield* TxQueue.fail(queue, "priority error") // awaitCompletion should succeed since queue is done (failures are not propagated) yield* TxQueue.awaitCompletion(queue) }))) it.effect("awaitCompletion concurrent behavior works", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Interrupt first, then await completion - should succeed immediately yield* TxQueue.interrupt(queue) yield* TxQueue.awaitCompletion(queue) }))) }) describe("legacy compatibility", () => { it.effect("isShutdown works as alias for isDone", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const shutdown1 = yield* TxQueue.isShutdown(queue) const done1 = yield* TxQueue.isDone(queue) assert.strictEqual(shutdown1, done1) assert.strictEqual(shutdown1, false) yield* TxQueue.shutdown(queue) const shutdown2 = yield* TxQueue.isShutdown(queue) const done2 = yield* TxQueue.isDone(queue) assert.strictEqual(shutdown2, done2) assert.strictEqual(shutdown2, true) }))) it.effect("shutdown() clears queue and marks as done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) yield* TxQueue.shutdown(queue) const size = yield* TxQueue.size(queue) const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(size, 0) assert.strictEqual(isDone, true) }))) }) describe("error channel behavior", () => { it.effect("take() on successfully interrupted queue interrupts", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // take() should interrupt on successfully interrupted queue const result = yield* Effect.exit(TxQueue.take(queue)) assert.strictEqual(result._tag, "Failure") }))) it.effect("take() on failed queue fails with error directly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.fail(queue, "test error") // take() should fail with the error directly const result = yield* Effect.flip(TxQueue.take(queue)) assert.strictEqual(result, "test error") }))) it.effect("take() on shutdown queue interrupts (success exit)", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.shutdown(queue) // shutdown creates success exit // take() should interrupt since shutdown creates success exit const result = yield* Effect.exit(TxQueue.take(queue)) assert.strictEqual(result._tag, "Failure") }))) it.effect("take() with explicit interrupt cause propagates interruption", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const interruptCause = Cause.interrupt(123) yield* TxQueue.failCause(queue, interruptCause) // take() should propagate the interruption directly, not wrap in Option const result = yield* Effect.exit(TxQueue.take(queue)) assert.strictEqual(result._tag, "Failure") // The cause should contain an interrupt, not a typed error if (result._tag === "Failure") { assert.strictEqual(Cause.hasInterrupts(result.cause), true) } }))) it.effect("take() with custom Cause.fail extracts error correctly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const customError = new Error("custom failure") const customCause = Cause.fail(customError) yield* TxQueue.failCause(queue, customCause) // take() should extract the custom error directly const result = yield* Effect.flip(TxQueue.take(queue)) assert.strictEqual(result, customError) }))) it.effect("take() with Cause.die (defect) propagates defect", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) const defect = new Error("unexpected defect") const defectCause = Cause.die(defect) yield* TxQueue.failCause(queue, defectCause) // take() should propagate the defect directly, not wrap in Option const result = yield* Effect.exit(TxQueue.take(queue)) assert.strictEqual(result._tag, "Failure") // The cause should contain a defect if (result._tag === "Failure") { assert.strictEqual(Cause.hasDies(result.cause), true) } }))) it.effect("take() with typed error cause extracts error directly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // Create a cause with typed error const typedError = "typed error" const failCause = Cause.fail(typedError) yield* TxQueue.failCause(queue, failCause) // take() should extract the typed error directly const result = yield* Effect.flip(TxQueue.take(queue)) assert.strictEqual(result, "typed error") }))) it.effect("peek() on successfully interrupted queue interrupts", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.interrupt(queue) // peek() should interrupt on interrupted queue const result = yield* Effect.exit(TxQueue.peek(queue)) assert.strictEqual(result._tag, "Failure") }))) it.effect("peek() on failed queue fails with error directly", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.fail(queue, "peek error") // peek() should fail with the error directly const result = yield* Effect.flip(TxQueue.peek(queue)) assert.strictEqual(result, "peek error") }))) it.effect("poll() on failed queue returns none", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 42) yield* TxQueue.fail(queue, "poll error") // poll() should return none on failed queue (doesn't propagate errors) const result = yield* TxQueue.poll(queue) assert.deepStrictEqual(result, Option.none()) }))) it.effect("takeN() with partial items available before failure", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2]) yield* TxQueue.fail(queue, "partial error") // takeN should fail even with some items available when queue is failed const result = yield* Effect.flip(TxQueue.takeN(queue, 3)) assert.strictEqual(result, "partial error") }))) it.effect("error propagation with different error types", () => Effect.tx(Effect.gen(function*() { const queue1 = yield* TxQueue.bounded(5) const queue2 = yield* TxQueue.bounded(5) const customError = new Error("custom error") const customObject = { code: 404, message: "not found" } yield* TxQueue.fail(queue1, customError) yield* TxQueue.fail(queue2, customObject) // Should propagate Error objects correctly const result1 = yield* Effect.flip(TxQueue.take(queue1)) assert.strictEqual(result1, customError) // Should propagate custom object errors correctly const result2 = yield* Effect.flip(TxQueue.take(queue2)) assert.deepStrictEqual(result2, customObject) }))) it.effect("closing queue allows remaining items to be consumed", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offerAll(queue, [1, 2, 3]) // Interrupt the queue (puts it in Closing state) yield* TxQueue.interrupt(queue) // Should still be able to take items const item1 = yield* TxQueue.take(queue) const item2 = yield* TxQueue.take(queue) assert.strictEqual(item1, 1) assert.strictEqual(item2, 2) // Queue should still be closing const isClosing = yield* TxQueue.isClosing(queue) assert.strictEqual(isClosing, true) // Take last item - should transition to Done const item3 = yield* TxQueue.take(queue) assert.strictEqual(item3, 3) // Queue should now be Done const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) }) }) describe("Done and end function", () => { it.effect("end() signals completion with Done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 1) yield* TxQueue.offer(queue, 2) // Signal the end of the queue const result = yield* TxQueue.end(queue) assert.strictEqual(result, true) // Queue should be closing since it has items const isClosing = yield* TxQueue.isClosing(queue) assert.strictEqual(isClosing, true) // Consume all items to transition to Done yield* TxQueue.takeAll(queue) // Queue should now be done const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) it.effect("take() on ended queue fails with Done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 42) yield* TxQueue.end(queue) // First take should still get the item since queue is in Closing state const item = yield* TxQueue.take(queue) assert.strictEqual(item, 42) // Second take should fail with Done since queue is now done const takeResult = yield* Effect.flip(TxQueue.take(queue)) assert.strictEqual(Cause.isDone(takeResult), true) assert.strictEqual(takeResult, Cause.Done()) }))) it.effect("end() works with TxEnqueue interface", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) yield* TxQueue.offer(queue, 1) const result = yield* TxQueue.end(queue) assert.strictEqual(result, true) // Queue should be closing since it has items const isClosing = yield* TxQueue.isClosing(queue) assert.strictEqual(isClosing, true) // Consume the item to transition to Done yield* TxQueue.take(queue) // Now verify queue is done const isDone = yield* TxQueue.isDone(queue) assert.strictEqual(isDone, true) }))) it.effect("end() returns false if queue is already done", () => Effect.tx(Effect.gen(function*() { const queue = yield* TxQueue.bounded(5) // End the queue once const result1 = yield* TxQueue.end(queue) assert.strictEqual(result1, true) // Try to end it again const result2 = yield* TxQueue.end(queue) assert.strictEqual(result2, false) }))) }) })