brillout / wildcard-api

Functions as API.
MIT License
368 stars 14 forks source link

TypeScript - Difficult to create types for better developer experience #21

Closed lostpebble closed 4 years ago

lostpebble commented 4 years ago

Hi Brillout! Hope things are well.

This is not a major issue, but it is something that has been nagging at me since I've started using Wildcard. The fact that I can't easily add types to my API. Specifically Typescript, but I'm sure it applies for most typing systems, as well as general IDE smart auto-complete stuff.

Wildcard works by pretty much defining things directly on the library module, and this creates issues when thinking about typing. For example:

wildcard.endpoints.postSearchQuery = function(args: IPostQueryArgs): IPostQueryResponse {
  return ESActionsOldPostIndex.queryTitleAndSubtitle(args);
};

wildcard.endpoints.deletePost = function(args: IPostDeleteArgs): IPostDeleteResponse {
  return FBActionsOldPost.deletePost(args);
};

We have nice types for our backend code. For example, postSearchQuery knows exactly what it expects with IPostQueryArgs and knows exactly what kind of object is returned with IPostQueryResponse. This is simply reusing the types which are already defined for our internal backend function ESActionsOldPostIndex.queryTitleAndSubtitle.

This issue arises now when I try and consumer this endpoint in the client. Because for Wildcard these functions have been "stuck on" directly on the library, and that the client wildcard library uses an entirely different module too, this type information cannot be reused on the client side without having to deliberately define it all over again.

Even the actual names of the API endpoints on our Wildcard API are not able to be typed / auto-completed:

import { endpoints } from "wildcard-api/client";

async function doSomething() {
  endpoints. // ??? our IDE knows nothing about our defined endpoint names or type information
}

I'm not sure this is a solvable issue. But it would really help loads with the developer experience of using Wildcard if it could be resolved somehow.

One way I could see it working is if we are able to define our endpoints in a central place, like a single class - which is then injected (or augmented) by Wildcard on the server - and from which the class type can be reused for the client functions.

lostpebble commented 4 years ago

I'm going to try and see what I can work out.

Just curios, would something like this work for Wildcard :

const endpoints = {
  postSearchQuery: function(args: IPostQueryArgs): IPostQueryResponse {
    return ESActionsOldPostIndex.queryTitleAndSubtitle(args);
  };
}

wildcard.endpoints = endpoints;

Or are we required to define the functions directly on wildcard.endpoints ?

Because I could potentially type the separated endpoints object and reuse that grouping of type information on the client.

Typescript has a useful type extraction keyword called typeof - so I could then extract my endpoints type information for my client using:

export type WildcardEndpoints = typeof endpoints;
brillout commented 4 years ago

I'm not familiar with TypeScript but AFAICT, a solution could be a babel plugin @wildcard-api/babel-transform-wildcard that does the following transformation:

Before:

// @remote
import {createTodo} from '../../server/endpoints.js';

After:

import {endpoints} from '@wildcard-api/client';
const {createTodo} = endpoints;

The idea is to trick your IDE into thinking that createTodo is imported directly from the server.

However, any type checking at build-time will not work. I'm assuming that you guys do type checking at dev-time with the IDE, correct?

Your IDE is not aware of any extra babel plugins, correct? How does your IDE support .vue files?

Note the changes of the new major version https://github.com/reframejs/wildcard-api/releases/tag/v0.2.3.

lostpebble commented 4 years ago

Okay, I think I've managed to solve my use case for now!

const wildcardEndpoints = {
  postSearchQuery: ESActionsOldPostIndex.queryTitleAndSubtitle,
  deletePost: FBActionsOldPost.deletePost,
};

wildcard.endpoints.postSearchQuery = wildcardEndpoints.postSearchQuery;
wildcard.endpoints.deletePost = wildcardEndpoints.deletePost;

export type TWildcardApi_Endpoints = typeof wildcardEndpoints;

And then on the client:

import { endpoints } from "wildcard-api/client";
import { TWildcardApi_Endpoints } from "../server/api/WildcardApi";

export const wildcard: TWildcardApi_Endpoints = endpoints;

Now the endpoints on my client are typed, under wildcard ! 😄

image

lostpebble commented 4 years ago

I'm not familiar with TypeScript but AFAICT, a solution could be a babel plugin @wildcard-api/babel-transform-wildcard that does the following transformation:

Before:

// @remote
import {createTodo} from '../../server/endpoints.js';

After:

import {endpoints} from '@wildcard-api/client';
const {createTodo} = endpoints;

The idea is to trick your IDE into thinking that createTodo is imported directly from the server.

However, any type checking at build-time will not work. I'm assuming that you guys do type checking at dev-time with the IDE, correct?

Your IDE is not aware of any extra babel plugins, correct? How does your IDE support .vue files?

Note the changes of the new major version https://github.com/reframejs/wildcard-api/releases/tag/v0.2.3.

Ah, that's not a bad idea. I just don't like using transforms where I don't have too - and I like being very deliberate with my types. The way I've solved it now feels good to me, as its more direct and natural.

I haven't worked with .vue files, but I would assume the IDE is capable. WebStorm is quite versatile and smart with most of the newer JavaScript tech nowadays.

I'll be sure to upgrade to the latest version soon 👍

brillout commented 4 years ago

Okay, I think I've managed to solve my use case for now!

Neat :+1:

brillout commented 4 years ago

Let me know if you want me to re-open this.

brillout commented 4 years ago

Btw https://github.com/reframejs/wildcard-api/issues/22 would be awesome. Thanks!

brillout commented 4 years ago

Thanks for testimonial!

Btw I'll be in Paris from the 21th to the 29th in case you'd be up for a drink. (I grew up in Paris.)

lostpebble commented 4 years ago

Oh cool. Yea I'd be up for that! Just heading away from the 24th, so would have to be one of the earlier days (this Saturday - Monday).

Do you perhaps use discord? Can add me on there as lostpebble#7413 - otherwise can DM on Twitter.