0no-co / wonka

🎩 A tiny but capable push & pull stream library for TypeScript and Flow
MIT License
709 stars 29 forks source link

Using `Wonka.fromObservable` in Reason API #38

Closed idkjs closed 4 years ago

idkjs commented 4 years ago

Lets say you have and observable graphql subscription which in JS looks like:

    useEffect(() => {
        const subscription = API.graphql(graphqlOperation(onCreateMessage)).subscribe({
            next: (event) => {
                if (event) {
                    console.log('Subscription: ' + JSON.stringify(event.value.data, null, 2));
                    console.log('EVENT: ' + JSON.stringify(event, null, 2));
                    setDisplay(true);
                    let message = event.value.data.onCreateMessage.message;
                    setMessage(message);
                }
            }
        });

The subscribe function is return a zen-observable which i believe is the same one returned by Wonka. See: https://github.com/aws-amplify/amplify-js/blob/86597db2b0704d7aff5b612557536142b82e1731/packages/api/src/API.ts#L16

How would I access the return value using Wonka.fromObservable?

This is what I have tried:

type t;
[@bs.module "@aws-amplify/api"] external api: t = "default";

[@bs.send]
external _graphqlSubWonka:
  (t, Types.graphqlOperation) => Wonka.observableT('a) =
  "graphql";

graphql is binding to API.ts#L352

In useEffect i have got:

  React.useEffect(() => {
    let subRequest = Graphql.OnCreateMessage.make();
    let graphqlOperation: Types.graphqlOperation = {
      query: subRequest##query,
      variables: subRequest##variables,
    };
    API.subWithWonka(graphqlOperation)
    |> Wonka.fromObservable((. result) => Js.log(result));
    None;
  });

This yield the following error which I do not understand how to read:

Error: This expression has type (. 'a) => unit
       but an expression was expected of type
         Wonka.observableT('b) = Wonka_observable.observableT('b)

I have tried a bunch of other things but just give this simple example to get the conversation started. Thank you for sharing your project.

idkjs commented 4 years ago

This seems to compile and log:

    let sub = API.subWithWonka(graphqlOperation);
    let _ = sub |> Wonka.fromObservable |> Wonka.subscribe((. x )=> Js.log2("obs",x));

So if x's type is:

let objectWithCallback: objectWithCallback = {
  next: Some(event => Js.log(event)),
  error: Some(errorValue => Js.log(errorValue)),
  closed: Some(true),
  complete: Some(_=>Js.log("complete"))
};

What is right way to work with result?

Any feedback would be greatly appreciated.

kitten commented 4 years ago

I'm not quite sure whether I have enough information here to give an informed answer, since some parts of this seem to be missing.

What for instance is API.subWithWonka and why does it return an Observable rather than a Wonka stream, if it does include the name "Wonka"?

There are multiple ways to work with results and those are all part of the "sinks" section here: https://wonka.kitten.sh/api/sinks

The question is what you're then doing with the result?

So suppose you have zen-observable's Observable, passing it into Wonka.fromObservable converts it into a Wonka stream. You can then subscribe to this stream using Wonka.subscribe which accepts a function that is called for each value in your stream and returns a subscription (which can be used to unsubscribe)

But depending on what you're doing you're probably then either looking to just consume this value or transform it?

idkjs commented 4 years ago

Not yet understanding the sinks and all, I hacked it with an Obj.magic call here:

https://github.com/idkjs/realtime-reason-aws/blob/eaddf08a56f536f8a13d46be6498079491838f39/src/aws/API.re#L45-L63

[@bs.send]
external _subscribe:
  (t, Types.graphqlOperation) =>
  Wonka.observableT(Types.observableLike(Types.observerLike('value))) =
  "graphql";
let subscribe = graphqlOperation => _subscribe(api, graphqlOperation);

let extractMessageFrom = event => {
  /* use Obj.magic to change event, otherwise code in Wonka.subcribe breaks. */
  let event = event->Obj.magic;
  /* get the message value on event and post to ui */
  let message = event##value##data##onCreateMessage##message;
  message;
};
/* setting up like this returns the message on which we can call `setMessage()` */
let subscribeToMessage = graphqlOperation =>
  _subscribe(api, graphqlOperation)
  |> Wonka.fromObservable
  |> Wonka.map((. event) => extractMessageFrom(event));

But depending on what you're doing you're probably then either looking to just consume this value or transform it?

I'm extracting the message value and setting on the screen.

So suppose you have zen-observable's Observable, passing it into Wonka.fromObservable converts it into a Wonka stream. You can then subscribe to this stream using Wonka.subscribe which accepts a function that is called for each value in your stream and returns a subscription (which can be used to unsubscribe)

Looks like I ended up with something like what you are describing. I am not clear on this part of the docs:

fromObservable

fromObservable transforms a spec-compliant JS Observable into a source. The resulting source will behave exactly the same as the Observable that it was passed, so it will start, end, and push values identically.

The original js subscription as noted in the orginal post, is:

useEffect(() => {
        const subscription = API.graphql(graphqlOperation(onCreateMessage)).subscribe({
            next: (event) => {
                if (event) {
                    console.log('Subscription: ' + JSON.stringify(event.value.data, null, 2));
                    console.log('EVENT: ' + JSON.stringify(event, null, 2));
                    setDisplay(true);
                    let message = event.value.data.onCreateMessage.message;
                    setMessage(message);
                }
            }
        });

I have that in reason as:

let listener: Types.observerLike('event) = {
  next: event => {
    Js.log2(
      "Subscription: ",
      Utils.jsonStringify(event.value.data, Js.Nullable.null, 2),
    );
    Js.log2("EVENT: ", Utils.jsonStringify(event, Js.Nullable.null, 2));
    let message = event.value.data.message;
    Js.log2("MESSAGE: ", Utils.jsonStringify(message, Js.Nullable.null, 2));
  },
  error: errorValue => Js.log(errorValue),
  complete: _ => Js.log("COMPLETE"),
};

I can't figure out how to pass that or if I should pass it. Is this the callback that should go in as as second argument?

Thanks again!

idkjs commented 4 years ago

I wrote this post up to try to get a handle on what I am seeing. https://dev.to/idkjs/realtime-reason-subscriptions-on-aws-with-wonka-57of. I would like to invite you to take a look at it and tear it apart. I think it would be a good start to putting some materials about Wonka in the wild. Even though I don't totally get it yet, this library feels in my gut, extremely important. Peace to you. Thanks for sharing.