Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Add Readonly-Streams (at least in Typings) #212

Open
Najros opened this issue Dec 21, 2020 · 3 comments
Open

Enhancement: Add Readonly-Streams (at least in Typings) #212

Najros opened this issue Dec 21, 2020 · 3 comments

Comments

@Najros
Copy link

Najros commented Dec 21, 2020

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:

const x = stream(5)
const squareX = x.pipe(map(x => x*x))

// squareX should not be writable!
squareX() // ok
squareX(9) // not ok!
x(3) // this is ok, instead

@nordfjord
Copy link
Collaborator

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?

@Najros
Copy link
Author

Najros commented Dec 21, 2020

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>&nbsp;</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>
  )
}

@nordfjord
Copy link
Collaborator

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/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants