import { assert, describe, it } from "@effect/vitest" import { Effect, Exit, Fiber } from "effect" import { TestClock } from "effect/testing" describe("Effect Eager Operations", () => { describe("fnUntracedEager", () => { describe("eager computation", () => { it("computes sync effects immediately", () => { let execCount = 0 const fn = Effect.fnUntracedEager(function*() { execCount++ yield* Effect.succeed(1) yield* Effect.succeed(2) return execCount }) const effect = fn() assert.strictEqual(execCount, 1) assert.strictEqual((effect as any)._tag, "Success") assert.strictEqual((effect as any).value, 1) }) it("computes failures immediately", () => { const fn = Effect.fnUntracedEager(function*() { yield* Effect.succeed(1) // @effect-diagnostics-next-line missingReturnYieldStar:off yield* Effect.fail("error") return "unreachable" }) const effect = fn() assert.strictEqual((effect as any)._tag, "Failure") }) it("stops at first async effect", () => { let syncSteps = 0 let asyncReached = false const fn = Effect.fnUntracedEager(function*() { syncSteps++ yield* Effect.succeed(1) syncSteps++ yield* Effect.promise(() => { asyncReached = true return Promise.resolve(2) }) syncSteps++ return "done" }) const effect = fn() assert.strictEqual(syncSteps, 2) assert.strictEqual(asyncReached, false) assert.notStrictEqual((effect as any)._tag, "Success") }) it("handles exceptions", () => { const fn = Effect.fnUntracedEager(function*() { yield* Effect.succeed(1) throw new Error("sync error") }) const effect = fn() assert.strictEqual((effect as any)._tag, "Failure") }) }) it.effect("executes sync generators", () => Effect.gen(function*() { const fn = Effect.fnUntracedEager(function*() { yield* Effect.succeed(1) yield* Effect.succeed(2) return "done" }) const result = yield* fn() assert.strictEqual(result, "done") })) it.effect("handles mixed sync/async effects", () => Effect.gen(function*() { const fn = Effect.fnUntracedEager(function*() { const a = yield* Effect.succeed(1) const b = yield* Effect.promise(() => Promise.resolve(2)) return a + b }) const result = yield* fn() assert.strictEqual(result, 3) })) it.effect("handles failures", () => Effect.gen(function*() { const fn = Effect.fnUntracedEager(function*() { // @effect-diagnostics-next-line missingReturnYieldStar:off yield* Effect.fail("error") return "unreachable" }) const exit = yield* Effect.exit(fn()) assert.isTrue(Exit.isFailure(exit)) })) it.effect("works with arguments and this context", () => Effect.gen(function*() { const obj = { value: 10, fn: Effect.fnUntracedEager(function*(this: { value: number }, x: number) { const a = yield* Effect.succeed(x) return a + this.value }) } const result = yield* obj.fn(5) assert.strictEqual(result, 15) })) it.effect("works with pipeable operators", () => Effect.gen(function*() { const fn = Effect.fnUntracedEager( function*() { yield* Effect.succeed(1) return 10 }, Effect.map((x: number) => x * 2) ) const result = yield* fn() assert.strictEqual(result, 20) })) it("proxies body length", () => { const fn = Effect.fnUntracedEager(function*(a: string, b: number, c: boolean) { return c ? a.length + b : b }) assert.strictEqual(fn.length, 3) }) it.effect("re-execution creates fresh iterators", () => Effect.gen(function*() { let execCount = 0 const fn = Effect.fnUntracedEager(function*() { execCount++ yield* Effect.succeed(1) yield* Effect.promise(() => Promise.resolve(2)) return execCount }) const effect = fn() const result1 = yield* effect assert.strictEqual(result1, 1) const result2 = yield* effect assert.strictEqual(result2, 2) assert.strictEqual(execCount, 2) })) it.effect("handles long sync chains efficiently", () => Effect.gen(function*() { const fn = Effect.fnUntracedEager(function*() { let sum = 0 for (let i = 0; i < 100; i++) { const val = yield* Effect.succeed(i) sum += val } return sum }) const result = yield* fn() assert.strictEqual(result, 4950) // sum of 0 to 99 })) }) describe("mapEager", () => { it.effect("successful effect", () => Effect.gen(function*() { const result = yield* Effect.mapEager(Effect.succeed(5), (n) => n * 2) assert.strictEqual(result, 10) })) it.effect("failed effect preserves failure", () => Effect.gen(function*() { const effect = Effect.fail("error") const mapped = Effect.mapEager(effect, (n) => n * 2) const exit = yield* Effect.exit(mapped) assert.strictEqual(exit._tag, "Failure", "Expected effect to fail") assert.ok(exit._tag === "Failure", "Type guard for exit failure") const failure = exit.cause.reasons.find((failure: any) => failure._tag === "Fail") assert.ok(failure, "Expected to find a Fail cause") assert.strictEqual(failure._tag, "Fail", "Expected failure to be a Fail type") assert.strictEqual((failure as any).error, "error", "Expected error to be preserved") })) it.effect("complex effect falls back to regular map", () => Effect.gen(function*() { const effect = Effect.mapEager(Effect.delay(Effect.succeed(10), 1), (n) => n + 5) const fiber = yield* Effect.forkChild(effect) yield* TestClock.adjust(1) const result = yield* Fiber.join(fiber) assert.strictEqual(result, 15) })) }) describe("flatMapEager", () => { it.effect("applies transformation eagerly for success effects", () => Effect.gen(function*() { let executions = 0 const effect = Effect.succeed(5) const flatMapped = Effect.flatMapEager(effect, (n) => { executions++ return Effect.succeed(n * 2) }) assert.strictEqual(yield* flatMapped, 10) assert.strictEqual(yield* flatMapped, 10) assert.strictEqual(yield* flatMapped, 10) assert.strictEqual(executions, 1) })) it.effect("preserves failure for failed effects", () => Effect.gen(function*() { const error = new Error("test error") const effect = Effect.fail(error) const flatMapped = Effect.flatMapEager(effect, (n) => Effect.succeed(n * 2)) const exit = yield* Effect.exit(flatMapped) assert.strictEqual(exit._tag, "Failure", "Expected effect to fail") assert.ok(exit._tag === "Failure", "Type guard for exit failure") const failure = exit.cause.reasons.find((failure: any) => failure._tag === "Fail") assert.ok(failure, "Expected to find a Fail cause") assert.strictEqual(failure._tag, "Fail", "Expected failure to be a Fail type") assert.strictEqual((failure as any).error, error, "Expected error to be preserved") })) it.effect("fallback to regular flatMap for complex effects", () => Effect.gen(function*() { const effect = Effect.delay(Effect.succeed(10), "1 millis") const flatMapped = Effect.flatMapEager(effect, (n) => Effect.succeed(n * 2)) const fiber = yield* Effect.forkChild(flatMapped) yield* TestClock.adjust("1 millis") const result = yield* Fiber.join(fiber) assert.strictEqual(result, 20) })) }) describe("mapErrorEager", () => { it.effect("preserves success for successful effects", () => Effect.gen(function*() { const effect = Effect.succeed(5) const mapped = Effect.mapErrorEager(effect, (err) => `mapped: ${err}`) const result = yield* mapped assert.strictEqual(result, 5) })) it.effect("applies transformation eagerly for failure effects", () => Effect.gen(function*() { const effect = Effect.fail("original error") const mapped = Effect.mapErrorEager(effect, (err) => `mapped: ${err}`) const exit = yield* Effect.exit(mapped) assert.strictEqual(exit._tag, "Failure", "Expected effect to fail") assert.ok(exit._tag === "Failure", "Type guard for exit failure") const failure = exit.cause.reasons.find((failure: any) => failure._tag === "Fail") assert.ok(failure, "Expected to find a Fail cause") assert.strictEqual(failure._tag, "Fail", "Expected failure to be a Fail type") assert.strictEqual((failure as any).error, "mapped: original error", "Expected error to be transformed") })) it.effect("fallback to regular mapError for complex effects", () => Effect.gen(function*() { const effect = Effect.delay(Effect.fail("error"), "1 millis") const mapped = Effect.mapErrorEager(effect, (err) => `mapped: ${err}`) const fiber = yield* Effect.forkChild(mapped) yield* TestClock.adjust("1 millis") const exit = yield* Fiber.await(fiber) assert.strictEqual(exit._tag, "Failure", "Expected effect to fail") assert.ok(exit._tag === "Failure", "Type guard for exit failure") const failure = exit.cause.reasons.find((failure: any) => failure._tag === "Fail") assert.ok(failure, "Expected to find a Fail cause") assert.strictEqual(failure._tag, "Fail", "Expected failure to be a Fail type") assert.strictEqual((failure as any).error, "mapped: error", "Expected error to be transformed") })) }) describe("mapBothEager", () => { it.effect("applies onSuccess eagerly for success effects", () => Effect.gen(function*() { const effect = Effect.succeed(5) const mapped = Effect.mapBothEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => n * 2 }) const result = yield* mapped assert.strictEqual(result, 10) })) it.effect("applies onFailure eagerly for failure effects", () => Effect.gen(function*() { const effect = Effect.fail("original error") const mapped = Effect.mapBothEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => n * 2 }) const exit = yield* Effect.exit(mapped) assert.strictEqual(exit._tag, "Failure", "Expected effect to fail") assert.ok(exit._tag === "Failure", "Type guard for exit failure") const failure = exit.cause.reasons.find((failure: any) => failure._tag === "Fail") assert.ok(failure, "Expected to find a Fail cause") assert.strictEqual(failure._tag, "Fail", "Expected failure to be a Fail type") assert.strictEqual((failure as any).error, "Failed: original error", "Expected error to be transformed") })) it.effect("fallback to regular mapBoth for complex effects", () => Effect.gen(function*() { const effect = Effect.delay(Effect.succeed(10), "1 millis") const mapped = Effect.mapBothEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => n * 2 }) const fiber = yield* Effect.forkChild(mapped) yield* TestClock.adjust("1 millis") const result = yield* Fiber.join(fiber) assert.strictEqual(result, 20) })) }) describe("catchEager", () => { it.effect("preserves success effects without applying catch", () => Effect.gen(function*() { const effect = Effect.succeed(42) const caught = Effect.catchEager(effect, (err) => Effect.succeed(`recovered: ${err}`)) const result = yield* caught assert.strictEqual(result, 42) })) it.effect("applies catch function eagerly for failure effects", () => Effect.gen(function*() { const effect = Effect.fail("original error") const caught = Effect.catchEager(effect, (err) => Effect.succeed(`recovered: ${err}`)) const result = yield* caught assert.strictEqual(result, "recovered: original error") })) it.effect("fallback to regular catch for complex effects", () => Effect.gen(function*() { const effect = Effect.delay(Effect.fail("error"), "1 millis") const caught = Effect.catchEager(effect, (err) => Effect.succeed(`recovered: ${err}`)) const fiber = yield* Effect.forkChild(caught) yield* TestClock.adjust("1 millis") const result = yield* Fiber.join(fiber) assert.strictEqual(result, "recovered: error") })) }) describe("matchEager", () => { it.effect("applies onSuccess eagerly for success effects", () => Effect.gen(function*() { const effect = Effect.succeed(42) const matched = Effect.matchEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => `Success: ${n}` }) const result = yield* matched assert.strictEqual(result, "Success: 42") })) it.effect("applies onFailure eagerly for failure effects", () => Effect.gen(function*() { const effect = Effect.fail("original error") const matched = Effect.matchEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => `Success: ${n}` }) const result = yield* matched assert.strictEqual(result, "Failed: original error") })) it.effect("fallback to regular match for complex effects", () => Effect.gen(function*() { const effect = Effect.delay(Effect.succeed(10), "1 millis") const matched = Effect.matchEager(effect, { onFailure: (err) => `Failed: ${err}`, onSuccess: (n) => `Success: ${n}` }) const fiber = yield* Effect.forkChild(matched) yield* TestClock.adjust("1 millis") const result = yield* Fiber.join(fiber) assert.strictEqual(result, "Success: 10") })) it.effect("eagerly handles complex failure scenarios", () => Effect.gen(function*() { const effect = Effect.fail(new Error("test error")) const matched = Effect.matchEager(effect, { onFailure: (err) => `Recovered from: ${err.message}`, onSuccess: (n) => `Success: ${n}` }) const result = yield* matched assert.strictEqual(result, "Recovered from: test error") })) it.effect("supports curried usage", () => Effect.gen(function*() { const matcher = Effect.matchEager({ onFailure: (err: string) => `Failed: ${err}`, onSuccess: (n: number) => `Success: ${n}` }) const successResult = yield* matcher(Effect.succeed(100)) assert.strictEqual(successResult, "Success: 100") const failureResult = yield* matcher(Effect.fail("error")) assert.strictEqual(failureResult, "Failed: error") })) it.effect("never fails - always produces a result", () => Effect.gen(function*() { const effect = Effect.fail("test error") const matched = Effect.matchEager(effect, { onFailure: (err) => `Handled: ${err}`, onSuccess: (n) => `Value: ${n}` }) // Should not throw, should produce a result const result = yield* matched assert.strictEqual(result, "Handled: test error") // Test with success case const successEffect = Effect.succeed(42) const successMatched = Effect.matchEager(successEffect, { onFailure: (err) => `Handled: ${err}`, onSuccess: (n) => `Value: ${n}` }) const successResult = yield* successMatched assert.strictEqual(successResult, "Value: 42") })) it.effect("performance: applies transformations immediately for resolved effects", () => Effect.gen(function*() { let onSuccessCount = 0 let onFailureCount = 0 const successEffect = Effect.succeed(10) const failureEffect = Effect.fail("error") const matcher = { onSuccess: (n: number) => { onSuccessCount++ return n * 2 }, onFailure: (err: string) => { onFailureCount++ return err.toUpperCase() } } // Multiple invocations of the same resolved effect should be efficient const successMatched = Effect.matchEager(successEffect, matcher) const failureMatched = Effect.matchEager(failureEffect, matcher) const result1 = yield* successMatched const result2 = yield* successMatched const result3 = yield* failureMatched const result4 = yield* failureMatched assert.strictEqual(result1, 20) assert.strictEqual(result2, 20) assert.strictEqual(result3, "ERROR") assert.strictEqual(result4, "ERROR") // Handlers should be called only once per effect execution due to eager evaluation assert.strictEqual(onSuccessCount, 1) assert.strictEqual(onFailureCount, 1) })) }) describe("matchCauseEager", () => { it("eagerly matches success effects", () => { let onSuccessCount = 0 let onFailureCount = 0 const successEffect = Effect.succeed(42) const matcher = { onSuccess: (value: number) => { onSuccessCount++ return value * 2 }, onFailure: (_cause: any) => { onFailureCount++ return "ERROR" } } const result = Effect.matchCauseEager(successEffect, matcher) // Should be resolved immediately assert.strictEqual((result as any)._tag, "Success") assert.strictEqual((result as any).value, 84) assert.strictEqual(onSuccessCount, 1) assert.strictEqual(onFailureCount, 0) }) it("eagerly matches failure effects", () => { let onSuccessCount = 0 let onFailureCount = 0 const failureEffect = Effect.fail("test error") const matcher = { onSuccess: (value: any) => { onSuccessCount++ return value * 2 }, onFailure: (_cause: any) => { onFailureCount++ return "HANDLED" } } const result = Effect.matchCauseEager(failureEffect, matcher) // Should be resolved immediately assert.strictEqual((result as any)._tag, "Success") assert.strictEqual((result as any).value, "HANDLED") assert.strictEqual(onSuccessCount, 0) assert.strictEqual(onFailureCount, 1) }) it.effect("falls back to regular matchCause for complex effects", () => Effect.gen(function*() { let onSuccessCount = 0 let onFailureCount = 0 const delayedEffect = Effect.delay(Effect.succeed(10), "10 millis") const matcher = { onSuccess: (value: number) => { onSuccessCount++ return value * 2 }, onFailure: (_cause: any) => { onFailureCount++ return "ERROR" } } const result = Effect.matchCauseEager(delayedEffect, matcher) // Should not be resolved immediately - should be a complex effect assert.notStrictEqual((result as any)._tag, "Success") assert.notStrictEqual((result as any)._tag, "Failure") // Verify handlers haven't been called yet assert.strictEqual(onSuccessCount, 0) assert.strictEqual(onFailureCount, 0) // Fork the effect and advance time const fiber = yield* Effect.forkChild(result) yield* TestClock.adjust("10 millis") const finalResult = yield* Fiber.join(fiber) // Now handlers should have been called assert.strictEqual(finalResult, 20) assert.strictEqual(onSuccessCount, 1) assert.strictEqual(onFailureCount, 0) })) it.effect("falls back to regular matchCause for delayed failure effects", () => Effect.gen(function*() { let onSuccessCount = 0 let onFailureCount = 0 const delayedFailure = Effect.delay(Effect.fail("delayed error"), "5 millis") const matcher = { onSuccess: (value: any) => { onSuccessCount++ return value * 2 }, onFailure: (_cause: any) => { onFailureCount++ return "HANDLED_DELAYED" } } const result = Effect.matchCauseEager(delayedFailure, matcher) // Should not be resolved immediately assert.notStrictEqual((result as any)._tag, "Success") assert.notStrictEqual((result as any)._tag, "Failure") // Verify handlers haven't been called yet assert.strictEqual(onSuccessCount, 0) assert.strictEqual(onFailureCount, 0) // Fork the effect and advance time const fiber = yield* Effect.forkChild(result) yield* TestClock.adjust("5 millis") const finalResult = yield* Fiber.join(fiber) // Now handlers should have been called assert.strictEqual(finalResult, "HANDLED_DELAYED") assert.strictEqual(onSuccessCount, 0) assert.strictEqual(onFailureCount, 1) })) it("handles cause information correctly", () => { let capturedCause: any = null const failureEffect = Effect.fail("test error") const matcher = { onSuccess: (value: any) => value, onFailure: (cause: any) => { capturedCause = cause return "handled" } } const result = Effect.matchCauseEager(failureEffect, matcher) // Should be resolved immediately assert.strictEqual((result as any)._tag, "Success") assert.strictEqual((result as any).value, "handled") assert.isNotNull(capturedCause) }) it.effect("preserves effect execution behavior for non-eager paths", () => Effect.gen(function*() { let executionCount = 0 const complexEffect = Effect.flatMap( Effect.delay(Effect.succeed(42), "1 millis"), (value) => { executionCount++ return Effect.succeed(value * 2) } ) const matcher = { onSuccess: (value: number) => value + 100, onFailure: (_cause: any) => "ERROR" } const result = Effect.matchCauseEager(complexEffect, matcher) // Should not be resolved immediately assert.notStrictEqual((result as any)._tag, "Success") assert.strictEqual(executionCount, 0) // Fork the effect and advance time const fiber = yield* Effect.forkChild(result) yield* TestClock.adjust("1 millis") const finalResult = yield* Fiber.join(fiber) // Should have executed the effect chain properly assert.strictEqual(finalResult, 184) // (42 * 2) + 100 assert.strictEqual(executionCount, 1) })) }) })