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?
}