import type * as Context from "@effect/data/Context" import * as Either from "@effect/data/Either" import * as Equal from "@effect/data/Equal" import * as List from "@effect/data/List" import type * as DataSource from "@effect/query/DataSource" import type * as Described from "@effect/query/Described" import type * as BlockedRequest from "@effect/query/internal_effect_untraced/blockedRequest" import * as _dataSource from "@effect/query/internal_effect_untraced/dataSource" import * as Parallel from "@effect/query/internal_effect_untraced/parallel" import * as Sequential from "@effect/query/internal_effect_untraced/sequential" /** * `BlockedRequests` captures a collection of blocked requests as a data * structure. By doing this the library is able to preserve information about * which requests must be performed sequentially and which can be performed in * parallel, allowing for maximum possible batching and pipelining while * preserving ordering guarantees. * * @internal */ export type BlockedRequests = Empty | Par | Seq | Single /** @internal */ export declare namespace BlockedRequests { /** @internal */ export interface Reducer { readonly emptyCase: () => Z readonly parCase: (left: Z, right: Z) => Z readonly singleCase: ( dataSource: DataSource.DataSource, blockedRequest: BlockedRequest.BlockedRequest ) => Z readonly seqCase: (left: Z, right: Z) => Z } } /** @internal */ export interface Empty { readonly _tag: "Empty" } /** @internal */ export interface Par { readonly _tag: "Par" readonly left: BlockedRequests readonly right: BlockedRequests } /** @internal */ export interface Seq { readonly _tag: "Seq" readonly left: BlockedRequests readonly right: BlockedRequests } /** @internal */ export interface Single { readonly _tag: "Single" readonly dataSource: DataSource.DataSource readonly blockedRequest: BlockedRequest.BlockedRequest } /** @internal */ export const empty: BlockedRequests = { _tag: "Empty" } /** * Combines this collection of blocked requests with the specified collection * of blocked requests, in parallel. * * @internal */ export const par = ( self: BlockedRequests, that: BlockedRequests ): BlockedRequests => ({ _tag: "Par", left: self, right: that }) /** * Combines this collection of blocked requests with the specified collection * of blocked requests, in sequence. * * @internal */ export const seq = ( self: BlockedRequests, that: BlockedRequests ): BlockedRequests => ({ _tag: "Seq", left: self, right: that }) /** * Constructs a collection of blocked requests from the specified blocked * request and data source. * * @internal */ export const single = ( dataSource: DataSource.DataSource, blockedRequest: BlockedRequest.BlockedRequest ): BlockedRequests => ({ _tag: "Single", dataSource, blockedRequest }) /** @internal */ export const MapDataSourcesReducer = ( f: (dataSource: DataSource.DataSource) => DataSource.DataSource ): BlockedRequests.Reducer> => ({ emptyCase: () => empty, parCase: (left, right) => par(left, right), seqCase: (left, right) => seq(left, right), singleCase: (dataSource, blockedRequest) => single(f(dataSource), blockedRequest) }) /** @internal */ export const ContramapContextReducer = ( f: Described.Described<(context: Context.Context) => Context.Context> ): BlockedRequests.Reducer> => ({ emptyCase: () => empty, parCase: (left, right) => par(left, right), seqCase: (left, right) => seq(left, right), singleCase: (dataSource, blockedRequest) => single( _dataSource.contramapContext(dataSource, f), blockedRequest ) }) type BlockedRequestsCase = ParCase | SeqCase interface ParCase { readonly _tag: "ParCase" } interface SeqCase { readonly _tag: "SeqCase" } /** * Transforms all data sources with the specified data source aspect, which * can change the environment type of data sources but must preserve the * request type of each data source. * * @internal */ export const mapDataSources = ( self: BlockedRequests, f: (dataSource: DataSource.DataSource) => DataSource.DataSource ): BlockedRequests => reduce(self, MapDataSourcesReducer(f)) /** * Provides each data source with part of its required environment. * * @internal */ export const contramapContext = ( self: BlockedRequests, f: Described.Described<(context: Context.Context) => Context.Context> ): BlockedRequests => reduce(self, ContramapContextReducer(f)) /** * Folds over the cases of this collection of blocked requests with the * specified functions. * * @internal */ export const reduce = ( self: BlockedRequests, reducer: BlockedRequests.Reducer ): Z => { let input = List.of(self) let output = List.empty>() while (List.isCons(input)) { const current = input.head switch (current._tag) { case "Empty": { output = List.cons(Either.right(reducer.emptyCase()), output) input = input.tail break } case "Par": { output = List.cons(Either.left({ _tag: "ParCase" }), output) input = List.cons(current.left, List.cons(current.right, input.tail)) break } case "Seq": { output = List.cons(Either.left({ _tag: "SeqCase" }), output) input = List.cons(current.left, List.cons(current.right, input.tail)) break } case "Single": { const result = reducer.singleCase(current.dataSource, current.blockedRequest) output = List.cons(Either.right(result), output) input = input.tail break } } } const result = List.reduce(output, List.empty(), (acc, current) => { switch (current._tag) { case "Left": { const left = List.unsafeHead(acc) const right = List.unsafeHead(List.unsafeTail(acc)) const tail = List.unsafeTail(List.unsafeTail(acc)) switch (current.left._tag) { case "ParCase": { return List.cons(reducer.parCase(left, right), tail) } case "SeqCase": { return List.cons(reducer.seqCase(left, right), tail) } } } case "Right": { return List.cons(current.right, acc) } } }) if (List.isNil(result)) { throw new Error("BUG: BlockedRequests.reduce - please report an issue at https://github.com/Effect-TS/query/issues") } return result.head } /** * Flattens a collection of blocked requests into a collection of pipelined * and batched requests that can be submitted for execution. * * @internal */ export const flatten = ( self: BlockedRequests ): List.List> => { let current = List.of(self) let updated = List.empty>() // eslint-disable-next-line no-constant-condition while (1) { const [parallel, sequential] = List.reduce( current, [Parallel.empty(), List.empty>()] as const, ([parallel, sequential], blockedRequest) => { const [par, seq] = step(blockedRequest) return [ Parallel.combine(parallel, par), List.concat(sequential, seq) ] } ) updated = merge(updated, parallel) if (List.isNil(sequential)) { return List.reverse(updated) } current = sequential } throw new Error( "BUG: BlockedRequests.flatten - please report an issue at https://github.com/Effect-TS/query/issues" ) } /** * Takes one step in evaluating a collection of blocked requests, returning a * collection of blocked requests that can be performed in parallel and a list * of blocked requests that must be performed sequentially after those * requests. */ const step = ( requests: BlockedRequests ): readonly [Parallel.Parallel, List.List>] => { let current: BlockedRequests = requests let parallel = Parallel.empty() let stack = List.empty>() let sequential = List.empty>() // eslint-disable-next-line no-constant-condition while (1) { switch (current._tag) { case "Empty": { if (List.isNil(stack)) { return [parallel, sequential] as const } current = stack.head stack = stack.tail break } case "Par": { stack = List.cons(current.right, stack) current = current.left break } case "Seq": { const left = current.left const right = current.right switch (left._tag) { case "Empty": { current = right break } case "Par": { const l = left.left const r = left.right current = par(seq(l, right), seq(r, right)) break } case "Seq": { const l = left.left const r = left.right current = seq(l, seq(r, right)) break } case "Single": { current = left sequential = List.cons(right, sequential) break } } break } case "Single": { parallel = Parallel.combine( parallel, Parallel.make(current.dataSource, current.blockedRequest) ) if (List.isNil(stack)) { return [parallel, sequential] } current = stack.head stack = stack.tail break } } } throw new Error( "BUG: BlockedRequests.step - please report an issue at https://github.com/Effect-TS/query/issues" ) } /** * Merges a collection of requests that must be executed sequentially with a * collection of requests that can be executed in parallel. If the collections * are both from the same single data source then the requests can be * pipelined while preserving ordering guarantees. */ const merge = ( sequential: List.List>, parallel: Parallel.Parallel ): List.List> => { if (List.isNil(sequential)) { return List.of(Parallel.toSequential(parallel)) } if (Parallel.isEmpty(parallel)) { return sequential } const seqHeadKeys = Sequential.keys(sequential.head) const parKeys = Parallel.keys(parallel) if ( seqHeadKeys.length === 1 && parKeys.length === 1 && Equal.equals(seqHeadKeys, parKeys) ) { return List.cons( Sequential.combine(sequential.head, Parallel.toSequential(parallel)), sequential.tail ) } return List.cons(Parallel.toSequential(parallel), sequential) }