1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Semaphore from "effect/Semaphore";
5import * as Option from "effect/Option";
6import * as Schema from "effect/Schema";
7
8export class AdmissionRejected extends Schema.TaggedError<AdmissionRejected>()(
9 "AdmissionRejected",
10 {},
11) {}
12
13export class Admission extends Context.Service<
14 Admission,
15 {
16 readonly withPermit: <A, E, R>(
17 store: boolean,
18 task: Effect.Effect<A, E, R>,
19 ) => Effect.Effect<A, E | AdmissionRejected, R>;
20 }
21>()("@t3tools/task-cache/Admission") {}
22
23const make = Effect.gen(function* () {
24 // Never queue fibers across Worker requests: their async contexts cannot cross request boundaries.
25 const stores = yield* Semaphore.make(2);
26 const reads = yield* Semaphore.make(4);
27 return Admission.of({
28 // The caller owns the request span; admission adds no second span per request.
29 withPermit: Effect.fnUntraced(function* (store, task) {
30 const result = yield* (store ? stores : reads).withPermitsIfAvailable(1)(task);
31 return Option.isSome(result) ? result.value : yield* new AdmissionRejected();
32 }),
33 });
34});
35
36export const layer = Layer.effect(Admission, make);