Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 2x 5x 5x 8x 8x 2x 5x 5x 5x 10x 2x 4x 4x 4x 2x 5x 5x 4x | import type { Candidate, Policy, PolicyContext } from './types.ts';
/**
* A policy that yields all candidates in their original order without filtering.
*
* Use as a placeholder when the sheaf always resolves to a single candidate
* (the policy is never actually called) or to express "try everything in
* declaration order" as an explicit policy.
*
* @param candidates - Candidates to yield in order.
* @yields Each candidate in the original array order.
*/
export async function* noopPolicy<M extends Record<string, unknown>>(
candidates: Candidate<Partial<M>>[],
): AsyncGenerator<Candidate<Partial<M>>, void, unknown[]> {
yield* candidates;
}
/**
* Proxy a policy coroutine, forwarding yielded candidates up and received
* error arrays down to the inner generator.
*
* Note: async generator `yield*` DOES forward `.next(value)` to the
* delegated async iterator, so for simple sequential composition (e.g.
* `fallthrough`) you can use `yield*` directly. `proxyPolicy` is the right
* primitive when you need to add logic between yields — for example,
* logging, counting attempts, or conditionally stopping early based on the
* error history.
*
* @param gen - The inner async generator to proxy.
* @yields Candidates from the inner generator.
* @returns void when the inner generator is exhausted.
* @example
* // Policy that logs each retry
* const withLogging = <M>(inner: Policy<M>): Policy<M> =>
* async function*(candidates, context) {
* const gen = inner(candidates, context);
* let next = await gen.next([]);
* while (!next.done) {
* const errors: unknown[] = yield next.value;
* if (errors.length > 0) console.log(`retry #${errors.length}`);
* next = await gen.next(errors);
* }
* };
* // The above pattern is exactly proxyPolicy with a side-effect added.
*/
export async function* proxyPolicy<M extends Record<string, unknown>>(
gen: AsyncGenerator<Candidate<Partial<M>>, void, unknown[]>,
): AsyncGenerator<Candidate<Partial<M>>, void, unknown[]> {
let next = await gen.next([]);
while (!next.done) {
const errors: unknown[] = yield next.value;
next = await gen.next(errors);
}
}
/**
* Filter candidates before passing to a policy.
*
* Returns the inner policy's generator directly — no proxying needed since
* this is a pure input transform that delegates entirely to the inner policy.
*
* @param predicate - Returns true for candidates that should be passed to the inner policy.
* @returns A policy combinator that filters its candidates before delegating.
*/
export const withFilter =
<M extends Record<string, unknown>>(
predicate: (
candidate: Candidate<Partial<M>>,
ctx: PolicyContext<M>,
) => boolean,
) =>
(inner: Policy<M>): Policy<M> =>
(candidates, context) =>
inner(
candidates.filter((candidate) => predicate(candidate, context)),
context,
);
/**
* Sort candidates by a comparator before passing to a policy.
*
* Returns the inner policy's generator directly — no proxying needed since
* this is a pure input transform that delegates entirely to the inner policy.
* The original candidates array is not mutated.
*
* @param comparator - Comparator function for sorting (same signature as Array.sort).
* @returns A policy combinator that sorts its candidates before delegating.
*/
export const withRanking =
<M extends Record<string, unknown>>(
comparator: (a: Candidate<Partial<M>>, b: Candidate<Partial<M>>) => number,
) =>
(inner: Policy<M>): Policy<M> =>
(candidates, context) =>
inner([...candidates].sort(comparator), context);
/**
* Try all candidates from policyA, then all candidates from policyB.
*
* Uses `yield*` directly since async generator delegation forwards
* `.next(value)` to the inner iterator, so error arrays are correctly
* threaded through each inner policy.
*
* policyB is not informed of policyA's failures at its prime call, but via
* `yield*` it receives all accumulated errors (including policyA's) as the
* argument to each subsequent `next(errors)` after its own failed attempts.
*
* @param policyA - First policy; its candidates are tried before policyB's.
* @param policyB - Fallback policy; only invoked after policyA is exhausted.
* @returns A combined policy that sequences policyA then policyB.
*/
export const fallthrough = <M extends Record<string, unknown>>(
policyA: Policy<M>,
policyB: Policy<M>,
): Policy<M> =>
async function* (candidates, context) {
yield* policyA(candidates, context);
yield* policyB(candidates, context);
};
|