/** Generically matches on a Reference/Collection's load method. */ type LoadLike < U > = { load ( ) : Promise < U > ; } ; /** Generically matches a property or zero-arg method that returns a promise. */ type PromiseFnLike < U > = ( ) => PromiseLike < U > ; /** Given a parent type P, and a new type V, returns V[] if P is already an array, i.e. we're in flatMap mode. */ type MaybeArray < P , V > = P extends ReadonlyArray < any > ? [ V ] extends [ ReadonlyArray < infer U > ] ? DropUndefined < U > [ ] : DropUndefined < V > [ ] : V ; /** Given a type T that we come across in the path, de-array it to continue our flatMap-ish semantics. */ type MaybeDropArray < T > = T extends ReadonlyArray < infer U > ? U : T ; type DropUndefined < T > = Exclude < T , undefined > ; /**
 * A type for declaratively walking the object graph.
 *
 * This is not technically a Lens that can `get/set`, but the idea is generally the same.
 *
 * `T` is the type we're on, i.e. when `T = Author` we can do `.firstName` or `.lastName.
 * `R` is either a `T` or `T[]` or `T | undefined` and just captures whether we've hit a
 * list at any point in the lens navigation path.
 */ export type Lens < T , R = T > = { [ P in keyof T ] : T [ P ] extends LoadLike < infer U > ? Lens < MaybeDropArray < DropUndefined < U > > , MaybeArray < R , U > > : T [ P ] extends PromiseFnLike < infer U > ? Lens < MaybeDropArray < DropUndefined < U > > , MaybeArray < R , U > > : Lens < MaybeDropArray < DropUndefined < T [ P ] > > , MaybeArray < R , T [ P ] > > ; } ; /**
 * Allows declaratively loading/traversing several layers of references at once.
 *
 * I.e.:
 *
 * ```typescript
 * const publisher = await book.load(b => b.author.publisher);
 * ```
 */ export async function loadLens < T , U , V > ( start , fn ) { const paths = collectPaths ( fn ) ; let current = start ; for await ( const path of paths ) { if ( Array . isArray ( current ) ) { current = ( await Promise . all ( current . map ( ( c ) => maybeLoad ( c , path ) ) ) ) . flat ( ) ; current = [ ... new Set ( current . filter ( ( c ) => c !== undefined ) ) ] ; } else { current = await maybeLoad ( current , path ) ; } } return current ! ; } function maybeLoad ( object , path ) { if ( object === undefined || object === null ) { return undefined ; } const value = object [ path ] ; if ( value && typeof value === "object" && "load" in value ) { return value . load ( ) ; } else if ( value && typeof value === "function" ) { return value . apply ( object ) ; } else { return value ; } } /**
 * The synchronous version of `loadLens`.
 *
 * This assumes you've first evaluated the lens with `loadLens` and now can access it synchronously.
 */ export function getLens < T , U , V > ( start , fn ) { const paths = collectPaths ( fn ) ; let current = start ; for ( const path of paths ) { if ( Array . isArray ( current ) ) { current = current . map ( ( c ) => maybeGet ( c , path ) ) . flat ( ) ; current = [ ... new Set ( current ) ] ; } else { current = maybeGet ( current , path ) ; } } return current ! ; } function maybeGet ( object , path ) { if ( object === undefined || object === null ) { return undefined ; } const value = object [ path ] ; if ( value && typeof value === "object" && "get" in value ) { if ( typeof value . get === "function" ) { return value . get ( ) ; } else { return value . get ; } } else if ( value && typeof value === "function" ) { return value . apply ( object ) ; } else { return value ; } } function collectPaths ( fn ) { const paths = [ ] ; const proxy = new Proxy ( { } , { get ( object , property , receiver ) { paths . push ( String ( property ) ) ; return receiver ; } , } ) ; fn ( proxy as any ) ; return paths ; }