import { Atom, AtomSeed, composeDesc, Desc, EventStream, EventStreamSeed, isAtomSeed, isEventStreamSeed, ScopedObservable, isPropertySeed, MethodDesc, Observable, ObservableSeed, Observer, Property, PropertySeed, Scope, Subscribe, } from "./abstractions" import { applyScopeMaybe } from "./applyscope" import { EventStreamSeedImpl } from "./eventstream" import { PropertySeedImpl } from "./property" import { AtomSeedImpl } from "./atom" import { HKT } from "./hkt" export type SubscriptionTransformer = ( subscribe: Subscribe ) => Subscribe export type StreamTransformer = SubscriptionTransformer export type Transformer = { changes: StreamTransformer init: (value: A) => B } export type StatefulTransformResult = O extends PropertySeed ? PropertySeed : O extends EventStreamSeed ? EventStreamSeed : never export type StatefulTransformResultScoped = O extends PropertySeed ? Property : O extends EventStreamSeed ? EventStream : never export type StatefulUnaryTransformResult = O extends AtomSeed ? AtomSeed : O extends PropertySeed ? PropertySeed : O extends EventStreamSeed ? EventStreamSeed : never export type StatefulUnaryTransformResultScoped = O extends AtomSeed ? Atom : O extends PropertySeed ? Property : O extends EventStreamSeed ? EventStream : never export type StatefulUnaryTransformResultFor = O extends AtomSeed ? AtomSeed : O extends PropertySeed ? PropertySeed : O extends EventStreamSeed ? EventStreamSeed : never export type StatefulUnaryTransformResultScopedFor = O extends AtomSeed ? Atom : O extends PropertySeed ? Property : O extends EventStreamSeed ? EventStream : never export interface GenericTransformOp { (o: In): StatefulUnaryTransformResult } export interface GenericTransformOpScoped { (o: In): StatefulUnaryTransformResultScoped } export interface BinaryTransformOp { (o: In): StatefulTransformResult } export interface BinaryTransformOpScoped { (o: In): StatefulTransformResultScoped } export interface StreamTransformOp { (seed: EventStreamSeed | EventStream): EventStreamSeed } export interface StreamTransformOpScoped { (seed: EventStreamSeed | EventStream): EventStream } export type In = HKT & ObservableSeed export interface UnaryTransformOp { (o: In): StatefulUnaryTransformResultFor } export interface UnaryTransformOpScoped { (o: In): StatefulUnaryTransformResultScopedFor } export const IdentityTransformer = { changes: (subscribe: Subscribe) => subscribe, init: (value: A) => value, } export function transform( desc: MethodDesc, transformer: Transformer ): UnaryTransformOp export function transform( desc: MethodDesc, transformer: Transformer, scope: Scope ): UnaryTransformOpScoped export function transform( desc: MethodDesc, transformer: Transformer ): BinaryTransformOp export function transform( desc: MethodDesc, transformer: Transformer, scope: Scope ): BinaryTransformOpScoped export function transform( desc: MethodDesc, transformer: StreamTransformer ): StreamTransformOp export function transform( desc: MethodDesc, transformer: StreamTransformer, scope: Scope ): StreamTransformOpScoped export function transform( methodCallDesc: MethodDesc, transformer: Transformer | StreamTransformer, scope?: Scope ): any { return (x: ObservableSeed) => { const desc = composeDesc(x, methodCallDesc) if (isEventStreamSeed(x)) { let transformFn = transformer instanceof Function ? transformer : transformer.changes const source = x.consume() return applyScopeMaybe( new EventStreamSeedImpl( desc, transformFn(source.subscribe.bind(source)) ), scope ) // TODO: should we always bind? } const t = transformer as Transformer if (isAtomSeed(x)) { const source = x.consume() return applyScopeMaybe( new AtomSeedImpl( desc, () => t.init(source.get()), transformPropertySubscribe(source, t), (newValue) => source.set(newValue as any as A /* A and B are equal for atoms */) ), scope ) } else if (isPropertySeed(x)) { const source = x.consume() return applyScopeMaybe( new PropertySeedImpl( desc, () => t.init(source.get()), transformPropertySubscribe(source, t) ), scope ) } else { throw Error("Unknown observable " + x) } } } function transformPropertySubscribe( src: { onChange: Subscribe }, transformer: Transformer ): Subscribe { if (src === undefined) throw Error("Assertion failed") return transformer.changes(src.onChange.bind(src)) // TODO: should we always bind? }