Skip to content

Proposal: alternative interface that's more aligned with current idioms #23

@dpup

Description

@dpup

Background

Currently the generated functions are exposed as static methods on a class. For example:

export class AccountService {
  static CreateAccount(
    this: void,
    req: CreateAccountRequest,
    initReq?: fm.InitReq,
  ): Promise<CreateAccountResponse> {
    return fm.fetchRequest<CreateAccountResponse>(`/api/accounts`, {
      ...initReq,
      method: 'POST',
      body: JSON.stringify(req, fm.replacer),
    });
  }
}

I don't know the original intent of this pattern, but you lose any benefits of having a Service instance that tracks state and you get typescript that's not very idiomatic.

import { AccountService } from './accounts.pb';

const resp = AccountService.CreateAccount(newAccount, {
  pathPrefix: API_HOST,
  credentials: 'include',
  headers: {
    'X-CSRF-Protection': 1
  }
})

Proposal

Introduce a flag use_static_classes which defaults to true for now. When use_static_classes is false, you simply get an exported function that maps to the method name:

export const createAccount = (
  req: CreateAccountRequest,
  initReq?: InitReq,
): Promise<CreateAccountResponse> => {
  {
    return fm.fetchRequest<CreateAccountResponse>(`/api/accounts`, {
      ...initReq,
      method: 'POST',
      body: JSON.stringify(req, fm.replacer),
    });
  }
};

And then:

import { createAccount } from './accounts.pb';

const resp = createAccount(newAccount, {
  pathPrefix: API_HOST,
  credentials: 'include',
  headers: {
    'X-CSRF-Protection': 1
  }
})

Problem : conflicting function names

The main downside I see in this approach is that there could be ambiguous function names in the case where a single proto file contains multiple services with the same method. This seems unlikely in my experience, but a possible fix could be to generate multiple TS files.

For example: consider accounts.proto contains AccountService and AdminService, both with createAccount methods.

We could generate:

  • accounts.pb.ts → all the message definitions.
  • accountservice.gw.pb.ts → functions for calling AccountService gateway endpoints
  • adminservice.gw.pb.ts → functions for calling AdminService gateway endpoints

Additional Variant

As well as exposing simple functions, we could also introduce a more classical Client. The client instance could be used to store request configuration:

export class AccountServiceClient {
  constructor(initReq: InitReq) {
    this.initReq = initReq;
  } 

  createAccount(
    this: void,
    req: CreateAccountRequest
  ): Promise<CreateAccountResponse> {
    return createAccount(req, this.initReq);
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions