Open Najros opened 3 years ago
I think I understand what you want. You want the types to be something like:
flyd.stream(v: T): WritableStream<T>
type Stream<T> = WritableStream<T> | ReadonlyStream<T>
map<T,V>(fn: (v: T)=> V) => (s: Stream<T>) => ReadonlyStream<V>
Did you run into a problem putting values into dependent streams where these types would've helped? What was the situation?
No, it is not a technical issue. It is all about a clear interface: if i expose a dependent stream, I do not want that anyone can put values in it.
Currently I created my own ReadonlyStream interface by copying the Stream interface and removing the unwanted methods (the setters and end stream)
export interface IReadonlyStream<T> {
(): T
map<V>(project: (value: T) => V): flyd.Stream<V>
ap<A, B>(this: flyd.Stream<(value: A) => B>, stream: flyd.Stream<A>): flyd.Stream<B>
chain<V>(project: (value: T) => flyd.Stream<V>): flyd.Stream<V>
of<V>(...values: V[]): flyd.Stream<V>
pipe<V>(operator: (input: flyd.Stream<T>) => flyd.Stream<V>): flyd.Stream<V>
["fantasy-land/map"]<V>(project: (value: T) => V): flyd.Stream<V>
["fantasy-land/ap"]<V>(fn: (value: flyd.Stream<T>) => V): flyd.Stream<V>
["fantasy-land/chain"]<V>(project: (value: T) => flyd.Stream<V>): flyd.Stream<V>
["fantasy-land/of"]<V>(...values: V[]): flyd.Stream<V>
val: T
hasVal: boolean
}
and I am doing an explicit cast like:
const squareX = x.pipe(map(x => x*x)) as IReadonlyStream<number>
But some methods have problems with that interface and then I need to do a cast back to flyd.Stream. (i.e. lift() does not accept ReadonlyStreams and the result can not even be casted easy to ReadonlyStream) In the end, I am doing a lot of castings and that is bad code style.
On the other hand, I am not sure, if ReadonlyStreams are a wanted feature in general, or if it is only a habit of mine. What is the use of dependent streams being writeable?
Cheers and thanks for the quick response.
PS: Maybe I can rework the typings in such a way and contribute it back to the project? I realy love flyd and use it as my state layer (as redux replacement) together with preact. That looks like this:
export default function WeekHeader(): VNode {
const start = useStream(SCurrentWeek) // SCurrentWeek is a ReadonlyStream that depends on SCurrentDate
const thisWeek = useStream(SThisWeek)
const setCurrent = useStreamUpdater(SCurrentDate) // a stream updater accepts a callback: (current) => next
const thisBtn = (
<button className="btn green" onClick={() => setCurrent(new Date())} disabled={thisWeek == start}>
<i className="material-icons">adjust</i>
</button>
)
return (
<div className="header">
<button className="btn" onClick={() => setCurrent(addWeeks(-1))}>
<i className="material-icons">chevron_left</i>
</button>
{thisBtn}
<button className="btn btn-flat" onClick={() => setCurrent(addMonths(-1))}>
<i className="material-icons">chevron_left</i>
</button>
<button className="btn btn-flat" onClick={() => setCurrent(addMonths(1))}>
<i className="material-icons">chevron_right</i>
</button>
<div className="title">
<h4 className="month">{format(start, "LLLL", localeOptions)}</h4>
<h4> </h4>
<h4 className="year">{format(start, "yyyy")}</h4>
</div>
<button className="btn btn-flat" onClick={() => setCurrent(addYears(-1))}>
<i className="material-icons">chevron_left</i>
</button>
<button className="btn btn-flat" onClick={() => setCurrent(addYears(1))}>
<i className="material-icons">chevron_right</i>
</button>
{thisBtn}
<button className="btn" onClick={() => setCurrent(addWeeks(1))}>
<i className="material-icons">chevron_right</i>
</button>
</div>
)
}
Hey again!
Maybe I can rework the typings in such a way and contribute it back to the project?
Absolutely! Feel free to submit a PR with reworked/better typings
What is the use of dependent streams being writeable?
It's less of a use case and more of "that's how it works internally."
I have however used it in one instance where data could come from multiple sources. I.e. a user entity had a feature flags object, but I also had a live subscription to feature flag updates
const user$ = stream()
const featureFlags$ = user$.map(prop('featureFlags'))
fetch('/user').then(x => x.json()).then(user$)
subscribeToFeatureFlagUpdates(featureFlags$)
copying the Stream interface and removing the unwanted methods (the setters and end stream)
I would definitely keep the end stream on there. There are cases where you'd want to end a dependent stream separately from its parent.
const x$ = stream(3)
const xx$ = x$.map(x => x * x)
const xxx$ = xx$.map(xx => xx * xx)
// does not end the parent stream, but does end the xxx$ stream
xx$.end(true)
I realy love flyd and use it as my state layer (as redux replacement) together with preact.
Nice! That's exactly how I started using it. albeit with mithril as the view layer.
check out @foxdonut 's meiosis for inspiration on stream based state management https://meiosis.js.org/
Currently there is no possibility to export a stream as readonly, to prevent the updating of a combined, piped or mapped stream. At least some typescript typings could enhance the proposed usage api doc for such an exported stream.
For example: