Originate / recouple

Declarative and type-safe API bridging. Write full-stack applications with ease.
MIT License
4 stars 1 forks source link

Extensible visitors #27

Closed astampoulis closed 6 years ago

astampoulis commented 6 years ago

This changes the base implementation of endpoints and middleware to allow for a generic visitor pattern, instead of hardcoding the specific mapServerData and mapClientData methods we used to have. That way we can separate the definition of endpoints and middleware from the definitions of visitors/"interpreters" of those types, which are defined by pattern matching on all the various cases.

As a result we could have the following package structure:

This does introduce a bit of complexity, as it requires the use of higher-kinded types for typing the generic visit function correctly and maintaining all type information. (I have added an explanation in the code about how higher-kinded types are encoded in Flow. An example of such a type we have used already is $ObjMap<Keys, F> -- F is a type-level function transforming the types of values of an object, and therefore has the higher kind Type -> Type.) I do think the extra complexity is well-warranted though; we have discussed with @sleexyz that being able to define new interpreters of the endpoint constructors is necessary in order to be able to add wrapper APIs for more libraries as independent NPM packages.

Closes #21 .

astampoulis commented 6 years ago

Here are my thoughts on these:

wrt. the HypotheticalMiddleware -- I see what you're saying, but I think let's cross that bridge if we get there. Maybe there's a way to adapt the HKT encoding to do that? Do you have an example of a middleware that would behave like that? My guess would be that most middleware just 'adds' to the input type rather than impose constraints on it/do transformations on it. I do think that we won't need a large number of middleware -- we'll need something for capture params, something for specifying outputs probably (e.g. JSON encoding vs. something else), maybe something for setting a route to be GET or POST eventually.. What else do you have in mind?

(BTW -- I think we might need to revisit the name middleware. It does translate to middleware on the server, but not on the client necessarily. What do you think of "endpoint combinators"? Or something like that.)

wrt. the client visitor result type -- the change was mostly motivated because I was trying to do capture params as an example where the HKT encoding was needed, and I made the switch there, and I thought it's a switch worth doing anyway. For capture params, needing the input to generate the data is much cleaner, I think: in the alternative, the intermediary structure would need to have the url be a function, which given the input parameter would return the actual URL, with the captured parameters substituted out. (Or something of the sort). I moved the change to this PR so that the HKT encoding does get used and tested.

Since the visitor result is mostly contained within the module that does the wrapping of a package (here, isomorphic-fetch), I think we can treat it as an implementation detail and we should thus go with the representation that makes that process easier. It might make sense to write unit tests for the result of the visitor as well (e.g. to check that the right URL gets generated), but I think we should be able to do that with both representations anyway.

sleexyz commented 6 years ago

With regards to the Client visitor, I agree that the function type makes it easier to implement and we should pick whatever is easier, since that's opaque to the library user anyways.

Yeah, I think sticking to adding inputs might be a fair assumption for now. It forces our library to be more declarative. If we support arbitrary input transformations then the library might be too powerful and therefore less "analyzable".


I'm fine renaming Middleware to something else, like Combinator now that we've transposed the logic to Visitors.