Web3ApiClient dev experience

Hi,
I am exploring how js dev can integrate a wrapper in their clients. I started with the following code.

import { Web3ApiClient } from '@web3api/client-js';

const client = new Web3ApiClient();

let response = client.query({
    uri: 'ens/api.helloworld.web3api.eth',
    query: `{
      logMessage(message: "Hello World!")
    }`,
  });
  response.then((result) => { 
      console.log("Result" + result);
      console.log("Result-Errors" + result.errors);
    })
  .catch(err => { 
      console.log("Error" + err); 
    });

A couple of observations

  1. I need to pass the query as a string. This string actually code, so I do not get the advantage of IDE(type checking, discoverability).
    following would be nice.
wrapper = client.initWrapper(uri: ''ens/api.helloworld.web3api.eth'')
wrapper.logMessage(message: "Hello world!");
  1. Do I need to specify full URL to fetch wrapper? would be nice if I specify a wrapper name. May be web3apiClient can derive the uri from registry?

  2. response I got from calling the client results in an error like, ‘No Web3API found at URI: w3://ens/api.helloworld.web3api.eth’. But promise catch is not called(then is invoked). So in my js it is not considered an error?

1 Like

Hi Sarath,

Thank you for your feedback!

Regarding your first question, we are moving away from graphql syntax. You’re already able to call client.invoke(...) on the client instead of client.query(...). While the graphql-based query syntax offers the advantage of concurrent queries, I find the invoke syntax offers a better user experience for reasons such as the one you mentioned. I think you’ll like it.

let response = client.invoke({
    uri: 'ens/api.helloworld.web3api.eth',
    module: 'query',
    method: 'logMessage',
    input: {
      message: "Hello World!",
    },
  });

We are also experimenting with other solutions that can offer a more familiar call syntax. One challenge here is cross-language support, including statically-typed languages. For example, if we are to add a new set of methods to the client, then we want to ensure TypeScript picks up changes to the client’s interface. This is not a trivial problem.

As for your second question, you do need to specify the full URI. I recommend creating a constant variable that holds your URI. Then you can just reference your variable.

const uri = "ens/api.helloworld.web3api.eth";

To answer your third question, when an exception is thrown in your wrapper’s WASM module, the error message is propagated to the client and stored as a string in the errors property of the return value of the query method, and in the error property in the return value of invoke. In your case, if you want to throw the exception you can use something like:

throw Error(result.errors[0]);
2 Likes

Thanks for the response Kris.

Regarding first one,
It might be difficult, ff we plan to add methods to client interface itself
‘’’
client.hello.logMessage(“Message”)
‘’’
But if we could
‘’’
wrapper = client.initWrapper()
wrapper.logMessage(“Message”)
‘’’
initWrapper method can generate a new dynamic object at add fields at runtime and return.
“dynamic object” concept is present in most of the prominent languages(C#, Python, JS, TS). I am not sure what other languages we are targeting.

1 Like

Thanks. Maybe you can help. As I understand it, the situation looks like this:

const wrapper: WrapperType = client.initWrapper<WrapperType>();
await wrapper.logMessage("Message");

In order to write this, TS needs prior knowledge of WrapperType. Without that knowledge, the TS transpiler will complain and the IDE won’t know which methods are available on the wrapper object.

We have plans to release a CLI tool that generates code for a class that encapsulates the client and contains a wrapper’s methods. Users will create a manifest file with information such as URI’s and desired namespaces, then run the CLI command w3 app codegen. Once the code is generated, usage will look like this:

const client: Web3ApiClient = new Web3ApiClient();
const app: PolywrapApp = new PolywrapApp(client);
app.foo.query.logMessage({ message: "Message" });

What do you think?

1 Like

It make sense. Code auto gen is right aproach instead of generating at runtime.
Given that 1) Which wrappers we refer to is available at build/coding time. 2) Generating at runtime involves latency and gc related complications.

I agree.

1 Like

Some improvements were just merged and can be used with the new cli app CLI command. You can add the CLI to your project using yarn add -D @web3api/cli 0.0.1-prealpha.67.

I encourage you to read more about them in this PR CLI command "app": TypeScript App Code Generator by dOrgJelli · Pull Request #726 · polywrap/monorepo · GitHub.

You’re welcome to join the discussion about future improvements on this topic in this PR TypeScript Application / Plugin Codegen Typing Enhancements · Issue #729 · polywrap/monorepo · GitHub.

const wrapper: WrapperType = client.initWrapper<WrapperType>();
await wrapper.logMessage("Message");

I really like this syntax! I can imagine the initWrapper returning errors like “missing plugin ‘foo’”, or “module environment sanitization failed: ‘Error: …’”.

I agree that run-time type-building is very interesting. This would however require run-time reflection of the wrapper modules, which means we’d be downloading more files into the client, and performing an algorithm that’s similar in complexity to full-on schema parsing. I think we should shy away from this, and do build-time stuff instead :slight_smile:

Going to cross link with the GitHub issue discussing these types of toolchain improvements: