export type ProducerResult = T | Promise | AsyncIterable export interface ProducerFn { (this: T): ProducerResult } export async function *produce(thisState: T, fn: ProducerFn): AsyncIterable { if (typeof fn !== "function") { return } let result = fn.call(thisState) if (isAsyncIterableProduced(result)) { return yield * result } if (isPromiseProduced(result)) { result = await result } if (isAsyncIterableProduced(result)) { return yield * result } return yield result function isAsyncIterableProduced(result: ProducerResult): result is AsyncIterable { function is(value: unknown): value is { [Symbol.asyncIterator]?: unknown } { return !!value } return ( is(result) && typeof result[Symbol.asyncIterator] === "function" ) } function isPromiseProduced(result: ProducerResult): result is AsyncIterable { function is(value: unknown): value is { then?: unknown } { return !!value } return ( is(result) && typeof result.then === "function" ) } } export async function *produceWithMutatingThis(fn: ProducerFn, initialThis?: T): AsyncIterable { let currentThis: T | undefined = initialThis ?? undefined function that(): T { if (!currentThis) { throw new Error("Please produce a value before accessing this") } return currentThis } const { proxy, revoke } = Proxy.revocable({} as T, { get(target: unknown, p: string | number | symbol): any { return Reflect.get(that(), p) }, set(): boolean { return false }, has(target: unknown, p: string | number | symbol): boolean { return Reflect.has(that(), p) }, getPrototypeOf() { return Object.getPrototypeOf(that()) }, setPrototypeOf() { return false }, isExtensible() { return false }, preventExtensions() { return true }, getOwnPropertyDescriptor(target: unknown, p: PropertyKey) { return Reflect.getOwnPropertyDescriptor(that(), p) }, deleteProperty() { return false }, defineProperty() { return false }, enumerate() { return [ ...Reflect.enumerate(that()) ] }, ownKeys() { return Reflect.ownKeys(that()) } }) for await (const nextState of produce(proxy, fn)) { currentThis = nextState yield nextState } revoke() }