docusign-esign-node-client icon indicating copy to clipboard operation
docusign-esign-node-client copied to clipboard

Why bro? TypeScript weirdness and OpenAPI generation

Open Arlen22 opened this issue 1 year ago • 6 comments

My history of working with DocuSign APIs in Typescript goes like this:

Yay! They have a node SDK. Why doesn't my DocuSign library work with webpack? Oh well, my project is only partly webpack, so just install it as an external dependency and it works fine. Now let me figure out how to get a JWT user token and make a client request.

Wait, why do they rename the response property? Now I have to override the response object. And I also have to inspect the options manually because the definitely typed library doesn't include the request and response types for random parameters.

import * as docusign from 'docusign-esign';
/** DOCUSIGN!!! WHY??? */
interface DocusignResponseHORROR<T> extends AxiosResponse<T> {
  data: never;
  body: AxiosResponse<T>["data"];
}
const client = new docusign.ApiClient();
const token: DocusignResponseHORROR<TokenInfo> = await client.requestJWTUserToken(...);
client.setBasePath(this.env.DOCUSIGN_BASEPATH);
client.addDefaultHeader('Authorization', 'Bearer ' + token.body.access_token);
const result = await new docusign.EnvelopesApi(client).createEnvelope(accountID, {
  // inspect EnvelopesAPI source code to determine parameters
});

Seriously? Why do I have to constantly inspect source code? Especially when I have to navigate to the source code manually because I'm using @types/docusign-esign and can't just ctrl-click? Wait a minute, what's this about it being manually generated? Oh, it's based on OpenAPI... and... I can generate it myself? Let me do that! Surely then none of this weirdness will happen.

Oh nice, I found @hey-api/openapi-ts.

const res = await Envelopes_PostEnvelopes({
  baseUrl: this.env.DOCUSIGN_BASEPATH,
  headers: { 'Authorization': 'Bearer ' + await this.getDocusignToken() },
  path: { accountId: this.env.DOCUSIGN_JWT_AccountId },
  query: { },
  body: envelope,
});

Wait, why am I getting type errors? After more digging through source code that I had to manually navigate, I figured out that the builder was trying to declare types verbatim without checking their type names and thus trying to declare primitive types. So I make a custom generator file which renames all the schema definitions.

import { createClient } from '@hey-api/openapi-ts';
import { resolve } from 'path';
import { existsSync,readFileSync } from "fs";
const file = "/root/docusign/OpenAPI-Specifications/esignature.rest.swagger-v2.1.json";
if (!existsSync(file)) {
  throw new Error(`File not found: ${file}`);
}


// DOCUSIGN!!! SERIOUSLY!!!
// because the definitions are directly built as types and because you can't override primitive types (`export type number = ...`), I have to rename the definitions.
const input = JSON.parse(readFileSync(file, 'utf8'), (key, value) => {
  if (key === "$ref" && value.startsWith("#/definitions/")) {
    return value.replace("#/definitions/", "#/definitions/def_");
  } else {
    return value;
  }
});
Object.entries(input.definitions).forEach(([key, value]) => {
  input.definitions[`def_${key}`] = value;
  delete input.definitions[key];
});

createClient({
  input,
  output: process.argv[2] || "docusign-client",
  client: '@hey-api/client-fetch',
  services: {
    methodNameBuilder(operation) {
      console.log(operation["x-ds-methodname"] || operation.id);
      return operation["x-ds-methodname"] || operation.id;
    },
  },
});

Great! Now I have a properly working third party library to access the DocuSign API.

Does everyone do this?

Arlen22 avatar Sep 30 '24 20:09 Arlen22

@Arlen22 Thank you for using the DocuSign SDK, and we regret the issues you're facing and would like to address them:

  1. Response Structure: While we use Axios internally, our response structure differs from the standard Axios response. This was done to avoid disruption from our previous HTTP client, SuperAgent (Due to unresolved security vulnerabilities, we switched to Axios). By defining our own structure, we ensure backward compatibility and avoid impacting existing integrations.
  2. @types/docusign-esign: The @types/docusign-esign package is not managed by DocuSign, but by a third party. Our SDKs are JavaScript-based, not TypeScript, so we understand the difficulty in finding definitions. We're actively working on improving SDKs and may offer more type-safe versions in the future.
  3. Using Our SDK: We recommend using our SDK over creating your own via OpenAPI, as it offers a more streamlined integration experience.

Thanks again for highlighting these issues, which helps us improve our SDKs.

sonawane-sanket avatar Jan 10 '25 08:01 sonawane-sanket

Using Our SDK: We recommend using our SDK over creating your own via OpenAPI, as it offers a more streamlined integration experience.

It was not streamlined.

Arlen22 avatar Jan 10 '25 21:01 Arlen22

It would be great to have first class TypeScript support from this SDK!

The third-party library is exhibiting some oddities. In particular, the third-party type library declares some types (e.g. EnvelopeDefinition, Notification, etc. ) as interfaces, when in fact they are callable functions in the original library. This generates a lot of type errors because you can't interact with interfaces the same way you can with a function or class.

prettyClouds avatar Feb 27 '25 19:02 prettyClouds

Hi ! It's 2025. TypeScript is the norm.

I mean there are a lot of options :

  • JSDoc-ing the library to offer typings
  • OpenAPI generation
  • Coding in TypeScript
  • Using an AI tool to do it for you

I of course understand that this has to be prioritized and planned. But it's 2025...

Clovel avatar Jun 11 '25 14:06 Clovel

I see it isn't on your roadmap. Is there anything we can do to help ?

Clovel avatar Jun 11 '25 14:06 Clovel

I would also like to ask if there's anything I can do to help support getting Typescript support for this SDK.

kublerjeff avatar Sep 30 '25 13:09 kublerjeff