Yaak Logo
Yaak

Request Chaining and Foundational Improvements

2024.8.0
2024.8.2

You might be thinking, a month of work for a single changelog item?!

Yes, indeed! But not only was Request Chaining the most-requested feature on the roadmap, it required an entire new concept (template functions) to be introduced, as well as a large restructuring of how plugins communicate with the application.

Let's first dig into Request Chaining, then talk about some of the changes that had to happen to get there.

⛓️ Request Chaining

When working with APIs, there are often dependencies between requests. For example, you might POST /users to create a new user, then PATCH /users/<id> to update the user's email address.

But how do you get the ID of the created user into the update? You'd better not be copy/pasting! There's now a better way.

Just like how environment variables allow dynamic reuse of values across requests, the new response(…) function allows something like PATCH /users/<id> to directly reference the id from the response body of POST /users. Here's how.

Step 1 → Select the response() function from the autocomplete menu in any text field.

Step 2 → Click the blue template tag to configure the function arguments.

URL bar

URL showcasing the use of the new response function

Step 3 → Select the request you want to depend on.

Step 4 → Enter either JSONPath or XPath query to select just the value you want.

Function editor

Function editor interface, for configuring arguments

Step 5 → Send the request!

Sending the request will render the template and inject the referenced value -- just like using regular variables. If no response exists yet for that request, it will be sent automatically.

So why did this take a month to ship? Let's cover that now.

Fixes Reference Response Values in other requests (chaining)

🏗️ Foundational Improvements

Template functions could have been implemented in a more direct way. However, I really wanted them to be extendable by the community. This meant having the necessary plugin APIs to provide dynamic template functions.

Here's a very simplified version of how the response() function plugin is implemented (or view the full source on GitHub).

// Psuedo-code for the response() template function plugin
export const plugin = {
  templateFunctions: [{
    name: 'response',
    args: [
      {type: 'http_request', name: 'request'},
      {type: 'text', name: 'path'},
    ],
    async onRender(ctx, args) {
      const requestId = args.values.request;
      const responses = await ctx.httpResponse.find({ requestId });
      return jsonPath.apply(responses[0].body, args.values.path);
    },
  }],
}

The big lift in this release was implementing the APIs for the ctx object (that you see above) to allow plugins to do useful things like show dialogs, copy text, fetch responses, and send requests. It's this plugin→app communication that needed to be implemented for this release.

I'm saving the full details for a dedicated blog post, but the gist is that there's now a bidirectional RPC API between the app (Rust) and plugin runtime (NodeJS). A functional interface is then implemented on top of this using strongly typed Request/Response events.

For example, the ctx.httpResponse.find({ requestId }) function emits a FindHttpRequestRequest{event_id: 123, ...} event to the app, then waits for a FindHttpRequestResponse{reply_id: 123, ...} to come back.

All event types are defined in Rust and TypeScript equivalents are generated using ts-rs, meaning plugin development is as easy as Ctrl+Spacing your way to success!

Editor autocomplete

Friendly autocomplete while writing plugin code

That's enough details for today, though. Look forward to a future blog post outlining the full plugin stack once the architecture has fully settled.

Fixes and Improvements

Patch 2024.8.1

  • Fix some plugins not executing on Windows, due to not finding the focused window