import type { DataSourceAspect } from './DataSourceAspect' import type { AnyRequest, Request } from './Request' import type { Chunk } from '@principia/base/Chunk' import type * as M from '@principia/base/Maybe' import type { _A, _E } from '@principia/base/prelude' import * as C from '@principia/base/Chunk' import * as E from '@principia/base/Either' import { IllegalArgumentError } from '@principia/base/Error' import { flow, pipe } from '@principia/base/function' import * as I from '@principia/base/IO' import { not } from '@principia/base/Predicate' import * as St from '@principia/base/Structural' import { tuple } from '@principia/base/tuple' import { isObject } from '@principia/base/util/predicates' import { CompletedRequestMap } from './CompletedRequestMap' import { Described } from './Described' export const DataSourceTypeId = Symbol('@principia/query/DataSource') export type DataSourceTypeId = typeof DataSourceTypeId const baseHash = St.hashString('@principia/query/DataSource') export class DataSource implements St.Equatable, St.Hashable { readonly [DataSourceTypeId]: DataSourceTypeId = DataSourceTypeId constructor( readonly identifier: string, readonly runAll: (requests: Chunk>) => I.IO ) {} [St.$equals](that: unknown): boolean { return isDataSource(that) && this.identifier === that.identifier } get [St.$hash]() { return St.combineHash(baseHash, St.hashString(this.identifier)) } } export function isDataSource(u: unknown): u is DataSource { return isObject(u) && DataSourceTypeId in u } export function batchN_(dataSource: DataSource, n: number): DataSource { return new DataSource(`${dataSource.identifier}.batchN(${n})`, (requests) => n < 1 ? I.halt(new IllegalArgumentError('batchN: n must be at least one', 'DataSource.batchN')) : dataSource.runAll(C.foldl_(requests, C.empty(), (b, a) => C.concat_(b, C.chunksOf_(a, n)))) ) } export function contramapIO_( dataSource: DataSource, f: Described<(b: B) => I.IO> ): DataSource { return new DataSource( `${dataSource.identifier}.contramapM(${f.description})`, flow(I.foreach(I.foreachC(f.value)), I.chain(dataSource.runAll)) ) } export function eitherWith_( ds1: DataSource, ds2: DataSource, f: Described<(c: C) => E.Either> ): DataSource { return new DataSource( `${ds1.identifier}.eitherWith(${ds2.identifier}, ${f.description})`, flow( I.foreach((rs) => { const [left, right] = C.partitionMap_(rs, f.value) return pipe( ds1.runAll(C.single(left)), I.crossWithC(ds2.runAll(C.single(right)), (a, b) => a.concat(b)) ) }), I.map(C.foldl(CompletedRequestMap.empty(), (b, a) => b.concat(a))) ) ) } export function equals(ds1: DataSource, ds2: DataSource): boolean { return ds1.identifier === ds2.identifier } export function gives_(ds: DataSource, f: Described<(r0: R0) => R>): DataSource { return new DataSource(`${ds.identifier}.giveSome(${f.description})`, flow(ds.runAll, I.gives(f.value))) } export function giveAll_(ds: DataSource, r: Described): DataSource { return gives_( ds, Described((_) => r.value, `(_) => ${r.description}`) ) } export function race_(ds1: DataSource, ds2: DataSource): DataSource { return new DataSource(`${ds1.identifier}.race(${ds2.identifier})`, (requests) => pipe(ds1.runAll(requests), I.race(ds2.runAll(requests))) ) } export class Batched extends DataSource { constructor(readonly identifier: string, readonly run: (requests: Chunk) => I.IO) { super( identifier, I.foldl(CompletedRequestMap.empty(), (m, r) => { const newRequests = C.filter_(r, not(m.contains)) if (C.isEmpty(newRequests)) { return I.succeed(m) } else { return pipe( this.run(newRequests), I.map((_) => m.concat(_)) ) } }) ) } } export function makeBatched( name: string, f: (requests: Chunk) => I.IO ): Batched { return new Batched(name, f) } export function fromFunction>(name: string, f: (a: A) => _A): DataSource { return new Batched( name, flow( C.foldl(CompletedRequestMap.empty(), (m, k) => m.insert(k, E.right(f(k)))), I.succeed ) ) } export function fromFunctionIOBatched( name: string, f: (a: Chunk) => I.IO, Chunk<_A>> ): DataSource { return new Batched(name, (requests: Chunk) => pipe( f(requests), I.match( (e): Chunk, _A>]> => C.map_(requests, (_) => tuple(_, E.left(e))), (bs): Chunk, _A>]> => C.zip_(requests, C.map_(bs, E.right)) ), I.map(C.foldl(CompletedRequestMap.empty(), (map, [k, v]) => map.insert(k, v))) ) ) } export function fromFunctionBatched>( name: string, f: (a: Chunk) => Chunk<_A> ): DataSource { return fromFunctionIOBatched(name, flow(f, I.succeed)) } export function fromFunctionIOBatchedOption( name: string, f: (a: Chunk) => I.IO, Chunk>>> ): DataSource { return new Batched(name, (requests: Chunk) => pipe( f(requests), I.match( (e): Chunk, M.Maybe<_A>>]> => C.map_(requests, (a) => tuple(a, E.left(e))), (bs): Chunk, M.Maybe<_A>>]> => C.zip_(requests, C.map_(bs, E.right)) ), I.map(C.foldl(CompletedRequestMap.empty(), (map, [k, v]) => map.insertOption(k, v))) ) ) } export function fromFunctionBatchedOption>( name: string, f: (a: Chunk) => Chunk>> ): DataSource { return fromFunctionIOBatchedOption(name, flow(f, I.succeed)) } export function fromFunctionIOBatchedWith( name: string, f: (a: Chunk) => I.IO, Chunk<_A>>, g: (b: _A) => A ): DataSource { return new Batched(name, (requests: Chunk) => pipe( f(requests), I.match( (e): Chunk, _A>]> => C.map_(requests, (a) => tuple(a, E.left(e))), (bs): Chunk, _A>]> => C.map_(bs, (b) => tuple(g(b), E.right(b))) ), I.map(C.foldl(CompletedRequestMap.empty(), (map, [k, v]) => map.insert(k, v))) ) ) } export function fromFunctionBatchedWith>( name: string, f: (a: Chunk) => Chunk<_A>, g: (b: _A) => A ): DataSource { return fromFunctionIOBatchedWith(name, flow(f, I.succeed), g) } export function fromFunctionIO( name: string, f: (a: A) => I.IO, _A> ): DataSource { return new Batched( name, flow( I.foreachC((a) => pipe( f(a), I.either, I.map((r) => tuple(a, r)) ) ), I.map(C.foldl(CompletedRequestMap.empty(), (map, [k, v]) => map.insert(k, v))) ) ) } export function fromFunctionIOOption( name: string, f: (a: A) => I.IO, M.Maybe<_A>> ): DataSource { return new Batched( name, flow( I.foreachC((a) => pipe( f(a), I.either, I.map((r) => tuple(a, r)) ) ), I.map(C.foldl(CompletedRequestMap.empty(), (map, [k, v]) => map.insertOption(k, v))) ) ) } export function fromFunctionOption>( name: string, f: (a: A) => M.Maybe<_A> ): DataSource { return fromFunctionIOOption(name, flow(f, I.succeed)) } export function applyAspect_( dataSource: DataSource, aspect: DataSourceAspect ): DataSource { return aspect.apply(dataSource) } export const never: DataSource = new DataSource('never', () => I.never)