A
A
Andrey Khokhlov2021-08-28 16:07:53
typescript
Andrey Khokhlov, 2021-08-28 16:07:53

How to use ES6 Proxy with TypeScript?

I tried to simplify the code examples as much as possible by removing unnecessary ones.
If someone is interested - a report on such an approach to the implementation of the service that we are trying to make friends with TypeScript

There is a service for working with the API (a class that is instantiated when the application starts).
When created, settings are passed to it, including a list of endpoints:

const apiClient = createApiClient({
  endpoints: {
    doTest: { url: '/test', method: 'get'},
  },
});


With the help of Proxy , instead

api.doRequest(endpoints.getData)

of doing something like virtual methods

api.getData()

, attempts to implement this in TypeScript lead either to an error

TS2339: Property 'getData' does not exist on type 'ApiClient'

type RequestMethod = 'get'| 'post';

type Endpoint = {
  method: RequestMethod;
  url: string;
};

type EndpointsConfig = Record<string, Endpoint>;

class ApiClient {
  endpoints: EndpointsConfig;

  constructor(endpoints: EndpointsConfig) {
    this.endpoints = endpoints;
  }

  doRequest(url: string, method: RequestMethod) : void {
    console.log(`url: ${url}`);
    console.log(`method: ${method}`);
  }
}

function createApiClient(endpoints: EndpointsConfig) {
  return new Proxy(new ApiClient(endpoints), {
    get(target: ApiClient, name: keyof ApiClient) {
      if (target.endpoints && target.endpoints[name]) {
        return () => (
          target.doRequest(target.endpoints[name].url, target.endpoints[name].method)
        );
      }

      return target[name];
    },
  });
}

const apiClient = createApiClient({
  getData: { url: '/data', method: 'get' },
});

apiClient.getData();



Or to the fact that you have to define endpoints not in the config, but directly hardcode in the service (which is already such a thing). At the same time, autocomplete even works, but

TS2722: Cannot invoke an object which is possibly 'undefined'.

import { Api } from './Api';

const endpoints = {
  getData: { url: '/data', method: 'get' },
};

interface IEndpoint {
  method: 'get'|'post';
  url: string,
}

type EndpointsConfig = {
  [K in keyof typeof endpoints]: IEndpoint;
};

type EndpointsProxy = {
  [K in keyof EndpointsConfig]?: () => unknown;
}

type IApiProxy = { [K in keyof Api]: Api[K] } & EndpointsProxy;

function createApi() : IApiProxy {
  return new Proxy(new Api(), {
    get(target: Api, name: keyof Api) {
      if (target.endpoints && target.endpoints[name]) {
        return () => (
          target.doRequest(target.endpoints[name])
        );
      }

      return target[name];
    },
  });
}

const api = createApi();

api.getData();



or

TS2322: Type 'Api' is not assignable to type 'IApiProxy'. Property 'getData' is missing in type 'Api' but required in type 'EndpointsProxy'


if you remove the optional endpoint methods

type EndpointsProxy = {
  [K in keyof EndpointsConfig]: () => unknown;
}


I will be grateful for help. Maybe I don’t understand / don’t see something, or maybe in general this approach is not applicable to TS.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
W
WbICHA, 2021-08-28
@andrhohlov

So?
https://www.typescriptlang.org/play?#code/C4TwDgpg...

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question