import { Dispatcher } from "./dispatcher" import { HKT } from "./hkt" import { Pipeable } from "./pipeable" import { toString } from "./tostring" import { GenericTransformOpScoped } from "./transform" export type TypeBitfield = number export const T_SCOPED = 0x0007 // scoped observables always implement seed and source interfaces as well export const T_SEED = 0x0002 export const T_SOURCE = 0x0004 export const T_PROPERTY = 0x0010 export const T_STREAM = 0x0020 export const T_ATOM = 0x0050 // atoms are always properties export const T_EVENT = 0x0100 export const T_SCOPE = 0x0200 export const T_VALUE = 0x1100 export const T_END = 0x2100 export function matchFlags(o: any, flags: TypeBitfield) { if (!o) return false return (o._L & flags) === flags } export function isProperty(e: any): e is Property { return matchFlags(e, T_SCOPED | T_PROPERTY) } export function isPropertySeed(e: any): e is PropertySeed { return matchFlags(e, T_SEED | T_PROPERTY) } export function isPropertySource(e: any): e is PropertySource { return matchFlags(e, T_SOURCE | T_PROPERTY) } export function isEventStream(e: any): e is EventStream { return matchFlags(e, T_SCOPED | T_STREAM) } export function isEventStreamSeed(e: any): e is EventStreamSeed { return matchFlags(e, T_SEED | T_STREAM) } export function isEventStreamSource(e: any): e is EventStreamSeed { return matchFlags(e, T_SOURCE | T_STREAM) } export function isAtom(e: any): e is Atom { return matchFlags(e, T_SCOPED | T_ATOM) } export function isAtomSeed(e: any): e is AtomSeed { return matchFlags(e, T_SEED | T_ATOM) } export function isAtomSource(e: any): e is AtomSeed { return matchFlags(e, T_SOURCE | T_ATOM) } export function isObservableSeed< V, C extends Observable, O extends ScopedObservable >(e: any): e is ObservableSeed { return e._L !== undefined } export type Callback = () => void export type Observer = (value: V) => void export type Subscribe = ( onValue: Observer, onEnd?: Observer ) => Unsub export type Unsub = Callback export class Description { desc: Desc constructor(desc: Desc) { this.desc = desc } toString(): string { if (typeof this.desc === "string") { return this.desc } else if (this.desc instanceof Description) { return this.desc.toString() } else if (this.desc.length == 2) { return `${toString(this.desc[0])}(${this.desc[1] .map(toString) .join(",")})` } else if (this.desc.length === 3) { return `${toString(this.desc[0])}.${this.desc[1]}(${this.desc[2] .map(toString) .join(",")})` } else { throw Error("Unexpected desc: " + toString(this.desc)) } } } export type Desc = Description | DescWithContext | MethodDesc | string export type MethodDesc = [string, any[]] export type DescWithContext = [any, string, any[]] export function composeDesc( context: any, methodCall: MethodDesc ): DescWithContext { const [method, args] = methodCall return [context, method, args] } export abstract class Event { _L: TypeBitfield = T_EVENT } export class Value extends Event { _L: TypeBitfield = T_VALUE value: V constructor(value: V) { super() this.value = value } } export class End extends Event { _L: TypeBitfield = T_END } export type EventLike = Event[] | Event | V export function toEvent(value: Event | V): Event { if (isEvent(value)) { return value } return valueEvent(value) } export function isEvent(value: any): value is Event { return matchFlags(value, T_EVENT) } export function toEvents(value: EventLike): Event[] { if (value instanceof Array) { return value.map(toEvent) } return [toEvent(value)] } export function valueEvent(value: V): Value { return new Value(value) } export function isValue(event: Event): event is Value { return matchFlags(event, T_VALUE) } export function isEnd(event: Event): event is End { return matchFlags(event, T_END) } export const endEvent: End = new End() export function valueObserver(observer: Observer): Observer> { return (event) => { if (isValue(event)) observer(event.value) } } export interface ObservableIdentifiers { _L: TypeBitfield // Discriminator bitfield for detecting implemented interfaces runtime. Used by the is* methods above. desc: Description toString(): string } export interface ForEach { forEach(observer: Observer): Unsub log(message?: string): Unsub } export type Observable = ObservableIdentifiers & ForEach & { subscribe(onValue: Observer, onEnd?: Observer): Unsub } export interface ObservableSeed< V, C extends Observable, O extends ScopedObservable > extends Pipeable, ObservableIdentifiers, ForEach { observableType(): string consume(): C applyScope(scope: Scope): O } export type ScopedObservable = Observable & { getScope(): Scope } export interface PropertyMethods { get(): V onChange(onValue: Observer, onEnd?: Observer | undefined): Unsub } export type PropertySource = Observable & PropertySeed & PropertyMethods & HKT> & { observableType(): "PropertySource" | "Property" | "AtomSource" | "Atom" } export type Property = ScopedObservable & PropertySource & PropertyMethods & HKT> & { observableType(): "Property" | "Atom" } export type PropertySeed = ObservableSeed< V, PropertySource, Property > & HKT> & { observableType(): | "PropertySeed" | "PropertySource" | "Property" | "AtomSeed" | "AtomSource" | "Atom" } export type EventStreamSource = Observable & EventStreamSeed & HKT> & { observableType(): "EventStreamSource" | "EventStream" | "Bus" } export type EventStream = ScopedObservable & EventStreamSource & HKT> & { observableType(): "EventStream" | "Bus" } export type EventStreamSeed = ObservableSeed< V, EventStreamSource, EventStream > & HKT> & { observableType(): | "EventStreamSeed" | "EventStreamSource" | "EventStream" | "Bus" } export type AtomSource = PropertySource & PropertySeed & AtomSeed & HKT> & { observableType(): "AtomSource" | "Atom" set(updatedValue: V): void } export type AtomSeed = ObservableSeed, Atom> & HKT> & { observableType(): "AtomSeed" | "AtomSource" | "Atom" } export type Atom = Property & AtomSource & HKT> & { observableType(): "Atom" set(newValue: V): void modify(fn: (old: V) => V): void } export interface Bus extends EventStream { push(newValue: V): void end(): void } export type ScopeFn = (onIn: () => Unsub) => void export type Scope = GenericTransformOpScoped & { _L: typeof T_SCOPE subscribe: ScopeFn } export function isScope(x: any): x is Scope { return matchFlags(x, T_SCOPE) } export type Function0 = () => R export type Function1 = (t1: T1) => R export type Function2 = (t1: T1, t2: T2) => R export type Function3 = (t1: T1, t2: T2, t3: T3) => R export type Function4 = (t1: T1, t2: T2, t3: T3, t4: T4) => R export type Function5 = ( t1: T1, t2: T2, t3: T3, t4: T4, t5: T5 ) => R export type Function6 = ( t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6 ) => R