staltz / experiment-push-pull-cyclejs

7 stars 1 forks source link

DOMSource PUSH/PULL proposal. With WebWorkers in mind. #5

Open aronallen opened 7 years ago

aronallen commented 7 years ago

Pseudo typings for my idea of DOMSource that supports both PUSH and PULL based API.

.element/ .elements is similar to Elements, it can query the selection with a sample, on local patches, or on global patches. PUSH or PULL .on is similar to .events() it is PUSH calling it on brings it closer to the DOM naming. .invoke is new, and it is PULL based.

the default selection is root of the isolation scope or the app.

SingleDOMSource = {
 select(selector: string) => SingleDOMSource 
 on(eventName: string, options: EventOptions, query: EventQueryTemplate) => Stream<Event>
 invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue>
 element(mode: 'self' | 'global' | Stream<any>  , query: ElementQueryTemplate) => Stream<Element>
}

MultiDOMSource = {
 select(selector: string) => SingleDOMSource 
 selectAll(selector: string) => MultiDOMSource 
 on(eventName: string, options: EventOptions, query: EventQueryTemplate) => Stream<Event>
 invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue[]>
 elements(mode: 'self' | 'global' | Stream<any>, query: ElementQueryTemplate) => Stream<Element[]>
}

GlobalDOMSource = {
 on(eventName: string, options: EventOptions, query: EventQueryTemplate) => Stream<Event>
 invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue>
 value(mode: 'self' | 'global' | Stream<any>  , query: GlobalQueryTemplate) => Stream<Element>
}

RootDOMSource = {
 document: GlobalDOMSource,
 window: GlobalDOMSource
 // akin to document.querySelect
 select(selector: string) => SingleDOMSource 
 // akin to document.querySelectAll
 selectAll(selector: string) => MultiDOMSource
 on(eventName: string, options: EventOptions, query: EventQueryTemplate) => Stream<Event>
 invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue>
 element(mode: 'self' | 'global' | Stream<any>  , query: ElementQueryTemplate) => Stream<Element>
}
staltz commented 7 years ago

cc @jvanbruegge

staltz commented 7 years ago

Totally unrelated to Push/Pull, but I like the Single vs Multi distinction, with select/selectAll and element/elements. Might get hard for JS users though, but tough luck for them...

aronallen commented 7 years ago

It would also help us get rid of this odd :root selector.

RootDOMSource .on, .invoke, and .element would always be tied to the root node.

I also propose three modes for .element/.elements/value (self, global and Stream) self would only emit when that component isolation scope changed. (maybe not relevant for document and window) global would emit on every patch. If you pass a stream, that would sample the value/elements at the stream speed.

jvanbruegge commented 7 years ago

Select and on/events is clear, but how exactly do you think invoke and elements should work? I think elements should look like this:

elements(): Signal<Element[]>

just from the semantics, the DOM is a value that changes over time (and thus a signal). So it would make sense for the quering function to return a Signal

staltz commented 7 years ago

The most revolutionary thing about this proposal is source.invoke(str, STREAMHERE$) because the only way Cycle.js apps pass streams to "the framework side" is using sinks. This proposal would short cut that by passing a stream to the source object. In my opinion, this can lead to confusion and lack of predictability. Also would break our future use cases of visual dataflow.

aronallen commented 7 years ago

So I see the potential for confusion. I am more a pragmatist than a theorist, for me the ability to make a nice source to sink data-flow graph is a cool feature. But at the end of the day, I need to get stuff done, the above was a proposal on how to enable invocation without changing too much. Remember my usecase is that it is impossible for me to reference the actual DOM node, as I am on a different thread.

The select and selectAll is more to move the singlular plural grammar further up the source chain. So we can invoke on just one element. The on is to more closely reflect how things are named in most frameworks and the DOM

aronallen commented 7 years ago

I don’t think the source should subscribe to the steam, it should just use it to build a new stream, that needs be a dependency of a sink, like every other stream.

staltz commented 7 years ago

The visual dataflow vision seems abstract at the moment, but that's just because it's a long term goal, so for now it's just a theoretical constraint, but in the future it will be very pragmatic. We want people to work on the visual dataflow chart as a pragmatic tool. And in order to get to that stage, we need to take decisions today that support getting there, that's why.

I don’t think the source should subscribe to the steam, it should just use it to build a new stream, that needs be a dependency of a sink, like every other stream.

Okay, this supports well the double driver idea that abaco put in the cyclejs issue you opened.

aronallen commented 7 years ago

Having several drivers may not be such a bad idea. Instead of DOMSource, imagine having DOMEvents, DOMIvocations and DOMElements.

staltz commented 7 years ago

Interesting that invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue> is actually a stream operator, much like sources.Time.delay(1000).

This lead me to think: how are we going to visualize source-provided operators in the dataflow diagram? Just as any other operator? (a box) Basic operators, from the perspective of the dataflow diagram, are globals, but source-provided operators would have to indicate somehow that they come from sources. (or would they not have to indicate?)

cc @Widdershin

aronallen commented 7 years ago

@staltz instead of invoke(methodName: string, arguments$: Stream<any>) => Stream<ReturnValue>. The type signature could be invoke = (methodName: string) => (arguments$: Stream<Arguments>) => Stream<ReturnValue>

staltz commented 7 years ago

I understand that helps for more flexible functional programming style, specially with helpers like Ramda. But I think we first need to address the issue of how to visualize source-provided operators. If we want to keep the association between operator and source visible and strong in the dataflow diagram, then the currying would actually disassociate the (returned) operator from the source object, whereas the original type signature would maintain the operator call site happening on the source object.

aronallen commented 7 years ago

@staltz the driver vendor could wrap their source operators in a function that "tags" the stream.