import type { Cache } from './Cache' import type { DataSource } from './DataSource' import type { DataSourceAspect } from './DataSourceAspect' import type { Result } from './internal/Result' import type { AnyRequest } from './Request' import type { Has, Tag } from '@principia/base/Has' import type { IO } from '@principia/base/IO' import type { _A, _E } from '@principia/base/prelude' import type * as P from '@principia/base/prelude' import * as A from '@principia/base/Array' import * as E from '@principia/base/Either' import * as Ex from '@principia/base/Exit' import * as FR from '@principia/base/FiberRef' import { flow, identity, pipe } from '@principia/base/function' import { mergeEnvironments } from '@principia/base/Has' import * as I from '@principia/base/IO' import * as Ca from '@principia/base/IO/Cause' import * as It from '@principia/base/Iterable' import * as L from '@principia/base/Layer' import * as Ma from '@principia/base/Managed' import * as M from '@principia/base/Maybe' import * as Ref from '@principia/base/Ref' import { tuple } from '@principia/base/tuple' import { matchTag } from '@principia/base/util/match' import { isObject } from '@principia/base/util/predicates' import { empty } from './Cache' import { Described } from './Described' import * as BlockedRequest from './internal/BlockedRequest' import * as BRS from './internal/BlockedRequests' import * as Cont from './internal/Continue' import { QueryContext } from './internal/QueryContext' import * as Res from './internal/Result' import { QueryFailure } from './QueryFailure' export const QueryTypeId = Symbol('@principia/query/Query') export type QueryTypeId = typeof QueryTypeId /** * A `Query` is a purely functional description of an effectual query * that may contain requests from one or more data sources, requires an * environment `R`, and may fail with an `E` or succeed with an `A`. * * Requests that can be performed in parallel, as expressed by `map2Par` and * combinators derived from it, will automatically be batched. Requests that * must be performed sequentially, as expressed by `map2` and combinators * derived from it, will automatically be pipelined. This allows for aggressive * data source specific optimizations. Requests can also be deduplicated and * cached. * * This allows for writing queries in a high level, compositional style, with * confidence that they will automatically be optimized. */ export class Query { readonly [QueryTypeId]: QueryTypeId = QueryTypeId constructor(readonly step: IO>) {} } export function isQuery(u: unknown): u is Query { return isObject(u) && QueryTypeId in u } /* * ------------------------------------------- * Run * ------------------------------------------- */ /** * Returns an effect that models executing this query with the specified * context. */ export function runContext_(ma: Query, queryContext: QueryContext): I.IO { return pipe( ma.step, I.gives((r: R) => [r, queryContext] as const), I.chain( matchTag({ Blocked: ({ blockedRequests, cont }) => I.apSecond_(BRS.run_(blockedRequests, queryContext.cache), Cont.runContext_(cont, queryContext)), Done: ({ value }) => I.succeed(value), Fail: ({ cause }) => I.failCause(cause) }) ) ) } /** * Returns an effect that models executing this query with the specified * context. */ export function runContext(queryContext: QueryContext): (ma: Query) => I.IO { return (ma) => runContext_(ma, queryContext) } /** * Returns an effect that models executing this query with the specified * cache. */ export function runCache_(ma: Query, cache: Cache): I.IO { return I.gen(function* (_) { const ref = yield* _(FR.make(true)) return yield* _(runContext_(ma, new QueryContext(cache, ref))) }) } /** * Returns an effect that models executing this query with the specified * cache. */ export function runCache(cache: Cache): (ma: Query) => I.IO { return (ma) => runCache_(ma, cache) } /** * Returns an effect that models executing this query, returning the query * result along with the cache. */ export function runLog(ma: Query): I.IO { return I.gen(function* (_) { const cache = yield* _(empty) const a = yield* _(runCache_(ma, cache)) return [cache, a] }) } export function run(ma: Query): I.IO { return pipe( ma, runLog, I.map(([, a]) => a) ) } /* * ------------------------------------------- * Folds * ------------------------------------------- */ /** * Recovers from all errors with provided Cause. */ export function catchAllCause_( ma: Query, h: (cause: Ca.Cause) => Query ): Query { return matchCauseQuery_(ma, h, succeed) } /** * Recovers from all errors with provided Cause. */ export function catchAllCause( h: (cause: Ca.Cause) => Query ): (ma: Query) => Query { return (ma: Query) => catchAllCause_(ma, h) } /** * Recovers from all errors. */ export function catchAll_( ma: Query, h: (e: E) => Query ): Query { return matchQuery_(ma, h, succeed) } /** * Recovers from all errors. */ export function catchAll( h: (e: E) => Query ): (ma: Query) => Query { return (ma: Query) => catchAll_(ma, h) } /** * A more powerful version of `matchM` that allows recovering from any type * of failure except interruptions. */ export function matchCauseQuery_( ma: Query, onFailure: (cause: Ca.Cause) => Query, onSuccess: (a: A) => Query ): Query { return new Query( I.matchCauseIO_( ma.step, (_) => onFailure(_).step, matchTag({ Blocked: ({ blockedRequests, cont }) => I.succeed(Res.blocked(blockedRequests, Cont.matchCauseQuery_(cont, onFailure, onSuccess))) as Query< R & R1 & R2, E1 | E2, B | C >['step'], Done: ({ value }) => onSuccess(value).step, Fail: ({ cause }) => onFailure(cause).step }) ) ) } /** * A more powerful version of `matchM` that allows recovering from any type * of failure except interruptions. */ export function matchCauseQuery( onFailure: (cause: Ca.Cause) => Query, onSuccess: (a: A) => Query ): (ma: Query) => Query { return (ma) => matchCauseQuery_(ma, onFailure, onSuccess) } /** * Recovers from errors by accepting one query to execute for the case of an * error, and one query to execute for the case of success. */ export function matchQuery_( ma: Query, onFailure: (error: E) => Query, onSuccess: (a: A) => Query ): Query { return matchCauseQuery_(ma, flow(Ca.failureOrCause, E.match(onFailure, failCause)), onSuccess) } /** * Recovers from errors by accepting one query to execute for the case of an * error, and one query to execute for the case of success. */ export function matchQuery( onFailure: (error: E) => Query, onSuccess: (a: A) => Query ): (ma: Query) => Query { return (ma) => matchQuery_(ma, onFailure, onSuccess) } /** * Folds over the failed or successful result of this query to yield a query * that does not fail, but succeeds with the value returned by the left or * right function passed to `fold`. */ export function match_( ma: Query, onFailure: (error: E) => B, onSuccess: (a: A) => C ): Query { return matchQuery_( ma, (e) => succeed(onFailure(e)), (a) => succeed(onSuccess(a)) ) } /** * Folds over the failed or successful result of this query to yield a query * that does not fail, but succeeds with the value returned by the left or * right function passed to `fold`. */ export function match( onFailure: (error: E) => B, onSuccess: (a: A) => C ): (ma: Query) => Query { return (ma) => match_(ma, onFailure, onSuccess) } /* * ------------------------------------------- * Sequential Apply * ------------------------------------------- */ export function ap_(fab: Query B>, fa: Query): Query { return crossWith_(fab, fa, (f, a) => f(a)) } export function ap( fa: Query ): (fab: Query B>) => Query { return (fab) => ap_(fab, fa) } export function crossFirst_(fa: Query, fb: Query): Query { return crossWith_(fa, fb, (a, _) => a) } export function crossFirst(fb: Query): (fa: Query) => Query { return (fa) => crossFirst_(fa, fb) } export function crossSecond_(fa: Query, fb: Query): Query { return crossWith_(fa, fb, (_, b) => b) } export function crossSecond( fb: Query ): (fa: Query) => Query { return (fa) => crossSecond_(fa, fb) } export function crossWith_( fa: Query, fb: Query, f: (a: A, b: B) => C ): Query { return new Query( I.chain_( fa.step, matchTag({ Blocked: ({ blockedRequests, cont }) => { if (cont._tag === 'Effect') { return I.succeed(Res.blocked(blockedRequests, Cont.effect(crossWith_(cont.query, fb, f)))) } else { return I.map_( fb.step, matchTag({ Blocked: (br) => Res.blocked(BRS.then(blockedRequests, br.blockedRequests), Cont.crossWith_(cont, br.cont, f)), Done: ({ value }) => Res.blocked( blockedRequests, Cont.map_(cont, (a) => f(a, value)) ), Fail: ({ cause }) => Res.failCause(cause) }) ) } }, Done: (a) => I.map_( fb.step, matchTag({ Blocked: ({ blockedRequests, cont }) => Res.blocked( blockedRequests, Cont.map_(cont, (b) => f(a.value, b)) ), Done: (b) => Res.done(f(a.value, b.value)), Fail: (e) => Res.failCause(e.cause) }) ), Fail: ({ cause }) => I.succeed(Res.failCause(cause)) }) ) ) } export function crossWith( fb: Query, f: (a: A, b: B) => C ): (fa: Query) => Query { return (fa) => crossWith_(fa, fb, f) } export function cross_( fa: Query, fb: Query ): Query { return crossWith_(fa, fb, tuple) } export function cross( fb: Query ): (fa: Query) => Query { return (fa) => cross_(fa, fb) } /* * ------------------------------------------- * Parallel Apply * ------------------------------------------- */ export function apC_( fab: Query B>, fa: Query ): Query { return crossWithC_(fab, fa, (f, a) => f(a)) } export function apC( fa: Query ): (fab: Query B>) => Query { return (fab) => apC_(fab, fa) } export function apFirstC_(fa: Query, fb: Query): Query { return crossWithC_(fa, fb, (a, _) => a) } export function apFirstC(fb: Query): (fa: Query) => Query { return (fa) => apFirstC_(fa, fb) } export function apSecondC_(fa: Query, fb: Query): Query { return crossWithC_(fa, fb, (_, b) => b) } export function apSecondC(fb: Query): (fa: Query) => Query { return (fa) => apSecondC_(fa, fb) } export function crossWithC_( fa: Query, fb: Query, f: (a: A, b: B) => C ): Query { return new Query( I.crossWithC_(fa.step, fb.step, (ra, rb) => { return ra._tag === 'Blocked' ? rb._tag === 'Blocked' ? Res.blocked(BRS.then(ra.blockedRequests, rb.blockedRequests), Cont.crossWithC_(ra.cont, rb.cont, f)) : rb._tag === 'Done' ? Res.blocked( ra.blockedRequests, Cont.map_(ra.cont, (a) => f(a, rb.value)) ) : Res.failCause(rb.cause) : ra._tag === 'Done' ? rb._tag === 'Blocked' ? Res.blocked( rb.blockedRequests, Cont.map_(rb.cont, (b) => f(ra.value, b)) ) : rb._tag === 'Done' ? Res.done(f(ra.value, rb.value)) : Res.failCause(rb.cause) : rb._tag === 'Fail' ? Res.failCause(Ca.both(ra.cause, rb.cause)) : Res.failCause(ra.cause) }) ) } export function crossWithC( fb: Query, f: (a: A, b: B) => C ): (fa: Query) => Query { return (fa) => crossWithC_(fa, fb, f) } export function crossC_( fa: Query, fb: Query ): Query { return crossWithC_(fa, fb, tuple) } export function crossC( fb: Query ): (fa: Query) => Query { return (fa) => crossC_(fa, fb) } /* * ------------------------------------------- * Batched Apply * ------------------------------------------- */ export function apBatched_( fab: Query B>, fa: Query ): Query { return crossWithBatched_(fab, fa, (f, a) => f(a)) } export function apBatched( fa: Query ): (fab: Query B>) => Query { return (fab) => apBatched_(fab, fa) } export function apFirstBatched_( fa: Query, fb: Query ): Query { return crossWithBatched_(fa, fb, (a, _) => a) } export function apFirstBatched( fb: Query ): (fa: Query) => Query { return (fa) => apFirstBatched_(fa, fb) } export function apSecondBatched_( fa: Query, fb: Query ): Query { return crossWithBatched_(fa, fb, (_, b) => b) } export function apSecondBatched( fb: Query ): (fa: Query) => Query { return (fa) => apSecondBatched_(fa, fb) } export function crossWithBatched_( fa: Query, fb: Query, f: (a: A, b: B) => C ): Query { return new Query( I.crossWith_(fa.step, fb.step, (ra, rb) => { return ra._tag === 'Blocked' ? rb._tag === 'Blocked' ? Res.blocked(BRS.then(ra.blockedRequests, rb.blockedRequests), Cont.crossWithBatched_(ra.cont, rb.cont, f)) : rb._tag === 'Done' ? Res.blocked( ra.blockedRequests, Cont.map_(ra.cont, (a) => f(a, rb.value)) ) : Res.failCause(rb.cause) : ra._tag === 'Done' ? rb._tag === 'Blocked' ? Res.blocked( rb.blockedRequests, Cont.map_(rb.cont, (b) => f(ra.value, b)) ) : rb._tag === 'Done' ? Res.done(f(ra.value, rb.value)) : Res.failCause(rb.cause) : rb._tag === 'Fail' ? Res.failCause(Ca.both(ra.cause, rb.cause)) : Res.failCause(ra.cause) }) ) } export function crossWithBatched( fb: Query, f: (a: A, b: B) => C ): (fa: Query) => Query { return (fa) => crossWithBatched_(fa, fb, f) } export function crossBatched_( fa: Query, fb: Query ): Query { return crossWithBatched_(fa, fb, tuple) } export function crossBatched( fb: Query ): (fa: Query) => Query { return (fa) => crossBatched_(fa, fb) } /* * ------------------------------------------- * Bifunctor * ------------------------------------------- */ export function bimap_(pab: Query, f: (e: E) => E1, g: (a: A) => B): Query { return matchQuery_(pab, flow(f, fail), flow(g, succeed)) } export function bimap(f: (e: E) => E1, g: (a: A) => B): (pab: Query) => Query { return (pab) => bimap_(pab, f, g) } export function mapError_(pab: Query, f: (e: E) => E1): Query { return bimap_(pab, f, identity) } export function mapError(f: (e: E) => E1): (pab: Query) => Query { return (pab) => mapError_(pab, f) } export function mapErrorCause_( pab: Query, h: (cause: Ca.Cause) => Ca.Cause ): Query { return matchCauseQuery_(pab, flow(h, failCause), succeed) } export function mapErrorCause( h: (cause: Ca.Cause) => Ca.Cause ): (pab: Query) => Query { return (pab) => mapErrorCause_(pab, h) } /* * ------------------------------------------- * MonadExcept * ------------------------------------------- */ export function subsumeEither(v: Query>): Query { return chain_(v, fromEither) } export function either(ma: Query): Query> { return match_(ma, E.left, E.right) } /* * ------------------------------------------- * Functor * ------------------------------------------- */ export function as_(fa: Query, b: B): Query { return map_(fa, () => b) } export function as(b: B): (fa: Query) => Query { return (fa) => as_(fa, b) } export function map_(fa: Query, f: (a: A) => B): Query { return new Query(I.map_(fa.step, Res.map(f))) } export function map(f: (a: A) => B): (fa: Query) => Query { return (fa) => map_(fa, f) } export function mapDataSources_(fa: Query, f: DataSourceAspect): Query { return new Query(I.map_(fa.step, Res.mapDataSources(f))) } export function mapDataSources(f: DataSourceAspect): (fa: Query) => Query { return (fa) => mapDataSources_(fa, f) } /* * ------------------------------------------- * Constructors * ------------------------------------------- */ export function defer(query: () => Query): Query { return flatten(fromIO(I.succeedLazy(query))) } export function halt(error: unknown): Query { return new Query(I.halt(error)) } export function fail(error: E): Query { return failCause(Ca.fail(error)) } export function fromIO(effect: IO): Query { return new Query( pipe( effect, I.matchCause(Res.failCause, Res.done), I.gives(([r, _]) => r) ) ) } export function fromEither(either: E.Either): Query { return pipe(succeed(either), chain(E.match(fail, succeed))) } export function fromOption(option: M.Maybe): Query, A> { return pipe(succeed(option), chain(M.match(() => fail(M.nothing()), succeed))) } export function fromRequest(request: A, dataSource: DataSource): Query, _A> { return new Query( pipe( I.ask(), I.chain(([, queryContext]) => I.chain_(FR.get(queryContext.cachingEnabled), (cachingEnabled) => { if (cachingEnabled) { return I.chain_( queryContext.cache.lookup(request), E.match( (ref) => I.succeed( Res.blocked( BRS.single(dataSource, BlockedRequest.make(request, ref)), Cont.make(request, dataSource, ref) ) ), (ref) => I.map_( ref.get, M.match(() => Res.blocked(BRS.empty, Cont.make(request, dataSource, ref)), Res.fromEither) ) ) ) } else { return I.map_(Ref.make, _A>>>(M.nothing()), (ref) => Res.blocked( BRS.single(dataSource, BlockedRequest.make(request, ref)), Cont.make(request, dataSource, ref) ) ) } }) ) ) ) } export function fromRequestUncached( request: A, dataSource: DataSource ): Query, _A> { return new Query( pipe( Ref.make(M.nothing, _A>>()), I.map((ref) => Res.blocked(BRS.single(dataSource, BlockedRequest.make(request, ref)), Cont.make(request, dataSource, ref)) ) ) ) } export function failCause(cause: Ca.Cause): Query { return new Query(I.succeed(Res.failCause(cause))) } export const never: Query = fromIO(I.never) export function nothing(): Query> { return succeed(M.nothing()) } export function succeed(value: A): Query { return new Query(I.succeed(Res.done(value))) } export function just(a: A): Query> { return succeed(M.just(a)) } /* * ------------------------------------------- * Monad * ------------------------------------------- */ export function chain_( ma: Query, f: (a: A) => Query ): Query { return new Query( I.chain_( ma.step, matchTag({ Blocked: ({ blockedRequests, cont }) => I.succeed(Res.blocked(blockedRequests, Cont.mapQuery_(cont, f))), Done: ({ value }) => f(value).step, Fail: ({ cause }) => I.succeed(Res.failCause(cause)) }) ) ) } export function chain( f: (a: A) => Query ): (ma: Query) => Query { return (ma) => chain_(ma, f) } export function flatten(mma: Query>): Query { return chain_(mma, identity) } /* * ------------------------------------------- * Reader * ------------------------------------------- */ export function ask(): Query { return fromIO(I.ask()) } export function asks(f: (_: R) => A): Query { return pipe(ask(), map(f)) } export function asksQuery(f: (_: R0) => Query): Query { return pipe(ask(), chain(f)) } export function gives_(ra: Query, f: Described<(r0: R0) => R>): Query { return new Query( pipe( ra.step, I.map((r) => Res.gives_(r, f)), I.gives(([r0, qc]) => [f.value(r0), qc] as const) ) ) } export function gives(f: Described<(r0: R0) => R>): (ra: Query) => Query { return (ra) => gives_(ra, f) } export function give_(ra: Query, r: Described): Query { return gives_( ra, Described(() => r.value, `() => ${r.description}`) ) } export function give(r: Described): (ra: Query) => Query { return (ra) => give_(ra, r) } export function giveSome_(ra: Query, r: Described): Query { return gives_( ra, Described((r0: R0) => ({ ...r0, ...r.value }), r.description) ) } export function giveSome(r: Described): (ra: Query) => Query { return (ra) => giveSome_(ra, r) } export function giveSomeLayer_( ra: Query, layer: Described> ): Query { return new Query( pipe( L.build(layer.value), Ma.gives(([r1, _]: readonly [R1, QueryContext]) => r1), Ma.result, Ma.use( Ex.matchIO( (c): IO> => I.succeed(Res.failCause(c)), (r) => gives_( ra, Described((r0: R & R1) => ({ ...r0, ...r }), layer.description) ).step ) ) ) ) } export function giveSomeLayer( layer: Described> ): (ra: Query) => Query { return (ra: Query) => new Query( pipe( L.build(layer.value), Ma.gives(([r1, _]: readonly [R1, QueryContext]) => r1), Ma.result, Ma.use( Ex.matchIO( (c): IO> => I.succeed(Res.failCause(c)), (r) => gives_( ra, Described((r0: R & R1) => ({ ...r0, ...r }), layer.description) ).step ) ) ) ) } export function giveServiceIO_( _: Tag ): (ma: Query, E, A>, f: Described>) => Query { return (ma: Query, E, A>, f: Described>): Query => asksQuery((r: R & R1) => chain_(fromIO(f.value), (t) => give_(ma, Described(mergeEnvironments(_, r, t), f.description))) ) } export function giveServiceIO(_: Tag) { return (f: Described>) => (ma: Query, E1, A1>): Query => giveServiceIO_(_)(ma, f) } export function giveService_(_: Tag): (ma: Query, E, A>, f: Described) => Query { return (ma, f) => giveServiceIO_(_)(ma, Described(I.succeed(f.value), f.description)) } export function giveService( _: Tag ): (f: Described) => (ma: Query, E1, A1>) => Query { return (f) => (ma) => giveService_(_)(ma, f) } /* * ------------------------------------------- * Filterable * ------------------------------------------- */ export function partitionQuery_( as: Iterable, f: (a: A) => Query ): Query, ReadonlyArray]> { return pipe(as, foreach(flow(f, either)), map(A.partitionMap(identity))) } export function partitionQuery( f: (a: A) => Query ): (as: Iterable) => Query, ReadonlyArray]> { return (as) => partitionQuery_(as, f) } export function partitonParQuery_( as: Iterable, f: (a: A) => Query ): Query, ReadonlyArray]> { return pipe(as, foreachC(flow(f, either)), map(A.partitionMap(identity))) } export function partitionParQuery( f: (a: A) => Query ): (as: Iterable) => Query, ReadonlyArray]> { return (as) => partitonParQuery_(as, f) } /* * ------------------------------------------- * Combinators * ------------------------------------------- */ function queryContext(): Query { return new Query(I.asks(([, queryContext]) => Res.done(queryContext))) } export function cached(ma: Query): Query { return gen(function* (_) { const context = yield* _(queryContext()) const cachingEnabled = yield* _(fromIO(FR.getAndSet_(context.cachingEnabled, true))) return yield* _(ensuring_(ma, fromIO(FR.set_(context.cachingEnabled, cachingEnabled)))) }) } export function ensuring_(ma: Query, finalizer: Query): Query { return matchCauseQuery_( ma, (cause1) => matchCauseQuery_( finalizer, (cause2) => failCause(Ca.then(cause1, cause2)), (_) => failCause(cause1) ), (value) => matchCauseQuery_( finalizer, (cause) => failCause(cause), () => succeed(value) ) ) } export function foreach_(as: Iterable, f: (a: A) => Query): Query> { return pipe( as, It.foldl(succeed([]) as Query>, (b, a) => crossWith_(b, f(a), (bs, b) => A.append(b)(bs))) ) } export function foreach(f: (a: A) => Query): (as: Iterable) => Query> { return (as) => foreach_(as, f) } export function foreachC_(as: Iterable, f: (a: A) => Query): Query> { return pipe( as, It.foldl(succeed([]) as Query>, (b, a) => crossWithC_(b, f(a), (bs, b) => A.append(b)(bs))) ) } export function foreachC(f: (a: A) => Query): (as: Iterable) => Query> { return (as) => foreachC_(as, f) } export function foreachBatched_( as: Iterable, f: (a: A) => Query ): Query> { return pipe( as, It.foldl(succeed([]) as Query>, (b, a) => crossWithBatched_(b, f(a), (bs, b) => A.append(b)(bs)) ) ) } export function foreachBatched( f: (a: A) => Query ): (as: Iterable) => Query> { return (as) => foreachBatched_(as, f) } export function getError(ma: Query, A>): Query> { return matchQuery_(ma, M.match(nothing, fail), just) } export function get(ma: Query>): Query, A> { return matchQuery_( ma, flow(M.just, fail), M.match(() => fail(M.nothing()), succeed) ) } export function getOrFail_(ma: Query>, e: E1): Query { return chain_( ma, M.match(() => fail(e), succeed) ) } export function getOrFail(e: E1): (ma: Query>) => Query { return (ma) => getOrFail_(ma, e) } export function left(ma: Query>): Query, A> { return matchQuery_( ma, flow(M.just, fail), E.match(succeed, () => fail(M.nothing())) ) } export function right(ma: Query>): Query, B> { return matchQuery_( ma, flow(M.just, fail), E.match(() => fail(M.nothing()), succeed) ) } export function leftOrFail_(ma: Query>, e: E1): Query { return chain_( ma, E.match(succeed, () => fail(e)) ) } export function leftOrFail(e: E1): (ma: Query>) => Query { return (ma) => leftOrFail_(ma, e) } export function leftOrFailWith_( ma: Query>, f: (right: B) => E1 ): Query { return chain_(ma, E.match(succeed, flow(f, fail))) } export function leftOrFailWith( f: (right: B) => E1 ): (ma: Query>) => Query { return (ma) => leftOrFailWith_(ma, f) } export function optional(ma: Query): Query> { return matchCauseQuery_( ma, flow( Ca.filterDefects((_) => !(_ instanceof QueryFailure)), M.match(() => nothing(), failCause) ), just ) } export function orHaltWith_(ma: Query, f: (e: E) => unknown): Query { return matchQuery_(ma, flow(f, halt), succeed) } export function orHaltWith(f: (e: E) => unknown): (ma: Query) => Query { return (ma) => matchQuery_(ma, flow(f, halt), succeed) } export function orHalt(ma: Query): Query { return orHaltWith_(ma, identity) } export function refineOrHalt_(ma: Query, pf: (e: E) => M.Maybe): Query { return refineOrHaltWith_(ma, pf, identity) } export function refineOrHalt( pf: (e: E) => M.Maybe ): (ma: Query) => Query { return (ma) => refineOrHalt_(ma, pf) } export function refineOrHaltWith_( ma: Query, pf: (e: E) => M.Maybe, f: (e: E) => unknown ): Query { return catchAll_(ma, (e) => pipe( pf(e), M.match(() => halt(f(e)), fail) ) ) } export function refineOrHaltWith( pf: (e: E) => M.Maybe, f: (e: E) => unknown ): (ma: Query) => Query { return (ma) => refineOrHaltWith_(ma, pf, f) } export function rightOrFail_(ma: Query>, e: E1): Query { return chain_( ma, E.match(() => fail(e), succeed) ) } export function rightOrFail(e: E1): (ma: Query>) => Query { return (ma) => rightOrFail_(ma, e) } export function rightOrFailWith_( ma: Query>, f: (left: A) => E1 ): Query { return chain_(ma, E.match(flow(f, fail), succeed)) } export function rightOrFailWith( f: (left: A) => E1 ): (ma: Query>) => Query { return (ma) => rightOrFailWith_(ma, f) } export function sandbox(ma: Query): Query, A> { return matchCauseQuery_(ma, fail, succeed) } export function unsandbox(v: Query, A>): Query { return mapErrorCause_(v, Ca.flatten) } export function sandboxWith_( ma: Query, f: (query: Query, A>) => Query, B> ): Query { return unsandbox(f(sandbox(ma))) } export function sandboxWith( f: (query: Query, A>) => Query, B> ): (ma: Query) => Query { return (ma) => sandboxWith_(ma, f) } export function summarized_( ma: Query, summary: I.IO, f: (start: B, end: B) => C ): Query { return pipe( fromIO(summary), cross(ma), crossWith(fromIO(summary), ([start, value], end) => [f(start, end), value]) ) } export function summarized( summary: I.IO, f: (start: B, end: B) => C ): (ma: Query) => Query { return (ma) => summarized_(ma, summary, f) } export function unrefineWith_( ma: Query, pf: (error: unknown) => M.Maybe, f: (e: E) => E1 ): Query { return catchAllCause_(ma, (cause) => pipe( cause, Ca.find(pf), M.match(() => pipe(cause, Ca.map(f), failCause), fail) ) ) } export function unrefineWith( pf: (error: unknown) => M.Maybe, f: (e: E) => E1 ): (ma: Query) => Query { return (ma) => unrefineWith_(ma, pf, f) } export function unrefine_(ma: Query, pf: (error: unknown) => M.Maybe): Query { return unrefineWith_(ma, pf, identity) } export function unrefine(pf: (error: unknown) => M.Maybe): (ma: Query) => Query { return (ma) => unrefine_(ma, pf) } export class GenQuery { readonly _R!: (_: R) => void readonly _E!: () => E readonly _A!: () => A constructor(readonly Q: Query) {} *[Symbol.iterator](): Generator, A, any> { return yield this } } const adapter = (_: any, __?: any) => { if (I.isIO(_)) { return new GenQuery(fromIO(_)) } return new GenQuery(_) } export function gen, A>( f: (i: { (_: Query): GenQuery (_: IO): GenQuery }) => Generator ): Query, P._E, A> { return defer(() => { const iterator = f(adapter as any) const state = iterator.next() function run(state: IteratorYieldResult | IteratorReturnResult): Query { if (state.done) { return succeed(state.value) } return chain_(state.value.Q, (value) => { const next = iterator.next(value) return run(next) }) } return run(state) }) }