import { __DEVBUILD__, assert } from "../_assert" import { NONE } from "../_core" import { toObs } from "../_interrop" import { isObservable, makeProperty } from "../_obs" import { Transaction } from "../_tx" import { isArray, isObject, slice } from "../_util" import { dispatcherOf, Observable } from "../Observable" import { Property } from "../Property" import { constant } from "../sources/single" import { Indexed, IndexedEndSubscriber, IndexedSource } from "./_indexed" import { JoinOperator } from "./_join" import { map } from "./map" import { toProperty } from "./toProperty" export type Combineable = Observable | T // TODO: fix combineAsArray typings export function combineAsArray(observables?: Array | T>): Property export function combineAsArray(o1: Observable | A): Property<[A]> export function combineAsArray(o1: Observable | A, o2: Observable | B): Property<[A, B]> export function combineAsArray( o1: Observable | A, o2: Observable | B, o3: Observable | C, ): Property<[A, B, C]> export function combineAsArray( o1: Observable | A, o2: Observable | B, o3: Observable | C, o4: Observable | D, ): Property<[A, B, C, D]> export function combineAsArray( o1: Observable | A, o2: Observable | B, o3: Observable | C, o4: Observable | D, o5: Observable | E, ): Property<[A, B, C, D, E]> export function combineAsArray( o1: Observable | A, o2: Observable | B, o3: Observable | C, o4: Observable | D, o5: Observable | E, o6: Observable | F, ): Property<[A, B, C, D, E, F]> export function combineAsArray(...observables: Array>): Property export function combineAsArray(...observables: any[]): Property { if (observables.length === 0) { return _combine(slice, []) } else if (isArray(observables[0])) { if (__DEVBUILD__) { assert(observables.length === 1, "Nested arrays are not supported by combine") } return _combine(slice, observables[0]) } else { return _combine(slice, observables) } } export type CombinedTemplate = { [K in keyof O]: O[K] extends Observable ? I : O[K] extends Record ? CombinedTemplate : O[K] } export function combineTemplate(template: T): Property> { const observables = [] as Array> const constants = [] as any[] function collect(obj: any): string { const props = [] as string[] Object.keys(obj).forEach(key => { const val: any = obj[key] const skey = JSON.stringify(key) if (isObservable(val)) { props.push(`${skey}:o[${observables.length}]`) observables.push(val) } else if (isObject(val)) { props.push(`${skey}:${collect(val)}`) } else { props.push(`${skey}:c[${constants.length}]`) constants.push(val) } }) return "{" + props.join(",") + "}" } const collected = collect(template) if (observables.length === 0) { return constant(template) as any } else { // tslint:disable-next-line:function-constructor const createPlainObject: any = new Function("o", "c", `return ${collected};`) const fn = (combined: any[]) => createPlainObject(combined, constants) return _combine(fn, observables) as any } } export function combineWith(f: (a: A) => R, observables: [Combineable]): Property export function combineWith( f: (a: A, b: B) => R, observables: [Combineable, Combineable], ): Property export function combineWith( f: (a: A, b: B, c: C) => R, observables: [Combineable, Combineable, Combineable], ): Property export function combineWith( f: (a: A, b: B, c: C, d: D) => R, observables: [Combineable, Combineable, Combineable, Combineable], ): Property export function combineWith( f: (a: A, b: B, c: C, d: D, e: E) => R, observables: [Combineable, Combineable, Combineable, Combineable, Combineable], ): Property export function combineWith( f: (a: A, b: B, c: C, d: D, e: E, f: F) => R, observables: [ Combineable, Combineable, Combineable, Combineable, Combineable, Combineable ], ): Property export function combineWith( f: (...vals: T[]) => R, observables: Array>, ): Property { return _combine((vals: T[]) => f(...vals), observables) } export function _combine( f: (vals: A[]) => B, observables: Array | A>, ): Property { let n = observables.length if (n === 0) { return map(f, constant([] as A[])) as Property } else if (n === 1) { return toProperty(map(val => f([val]), toObs>(observables[0]))) } else { const sources = Array(n) while (n--) { sources[n] = dispatcherOf(toObs(observables[n])) } return makeProperty(new Combine(new IndexedSource(sources), f)) } } class Combine extends JoinOperator, B> implements IndexedEndSubscriber { private vals: A[] private has: boolean private nWaitV: number private nWaitE: number constructor(source: IndexedSource, private f: (vals: A[]) => B) { super(source) source.setEndSubscriber(this) let n = (this.nWaitE = this.nWaitV = source.size()) this.has = false this.vals = Array(n) while (n--) this.vals[n] = NONE } public dispose(): void { // Ended streams/props will re-emit the end event after activation so we need // to reset the end wait counter. However, only properties emit their latest // value so we can't reset that information - otherwise we might lose some information // between activations this.nWaitE = this.vals.length super.dispose() } public next(tx: Transaction, ival: Indexed): void { const prev = this.vals[ival.idx] this.vals[ival.idx] = ival.val if ((prev === NONE && --this.nWaitV === 0) || this.nWaitV === 0) { this.has = true this.fork(tx) } } public error(tx: Transaction, err: Error): void { this.forkError(tx, err) } public iend(tx: Transaction, idx: number, source: IndexedSource): void { source.disposeIdx(idx) if (--this.nWaitE === 0) { this.forkEnd(tx) } } public join(tx: Transaction): void { if (this.has === true) { const { f } = this this.has = false this.sink.next(tx, f(this.vals)) } super.join(tx) } }