t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/Util/Memoize.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

82 lines
2.7 KiB
TypeScript

import { cachedInScope } from "@/Util/Memoize.ts";
import { describe, expect, it } from "alchemy-test";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Deferred from "effect/Deferred";
import * as Exit from "effect/Exit";
import * as Fiber from "effect/Fiber";
import * as Scope from "effect/Scope";
// `Effect.cached` runs the computation on the first caller's fiber, so
// interrupting that caller while others wait fails every waiter with an
// interrupt-only cause and leaves the cache poisoned. These pin the
// behaviour `cachedInScope` exists for.
describe("cachedInScope", { tags: ["unit", "local"] }, () => {
it.live("survives the first caller being interrupted", () =>
Effect.gen(function* () {
const scope = yield* Scope.make();
let runs = 0;
const memo = yield* cachedInScope(scope)(
Effect.sleep("100 millis").pipe(
Effect.map(() => {
runs++;
return 42;
}),
),
);
const owner = yield* Effect.forkChild(memo);
yield* Effect.sleep("10 millis");
const waiter = yield* Effect.forkChild(memo);
yield* Effect.sleep("10 millis");
yield* Fiber.interrupt(owner);
expect(yield* Fiber.join(waiter)).toBe(42);
expect(yield* memo).toBe(42);
expect(runs).toBe(1);
yield* Scope.close(scope, Exit.void);
}),
);
it.live("runs once and shares failures with every waiter", () =>
Effect.gen(function* () {
const scope = yield* Scope.make();
let runs = 0;
const memo = yield* cachedInScope(scope)(
Effect.suspend(() => {
runs++;
return Effect.fail("boom");
}),
);
const [a, b] = yield* Effect.all(
[Effect.result(memo), Effect.result(memo)],
{ concurrency: "unbounded" },
);
expect(a._tag).toBe("Failure");
expect(b._tag).toBe("Failure");
expect(runs).toBe(1);
yield* Scope.close(scope, Exit.void);
}),
);
it.live(
"closing the scope cancels the computation for its waiters",
() =>
Effect.gen(function* () {
const scope = yield* Scope.make();
const started = yield* Deferred.make<void>();
const memo = yield* cachedInScope(scope)(
Deferred.succeed(started, undefined).pipe(
Effect.andThen(Effect.never),
),
);
const waiter = yield* Effect.forkChild(memo);
yield* Deferred.await(started);
yield* Scope.close(scope, Exit.void);
const exit = yield* Fiber.await(waiter);
expect(
Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause),
).toBe(true);
}),
{ timeout: 5_000, retry: 0 },
);
});