Lune TS client
For the production of models, schemas and services we are using https://github.com/ferdikoomen/openapi-typescript-codegen. It is a tool we were already using on the dashboard and it overall produces the best results for our use case. There are some adaptations we do the generated services, but they are minimal. We do not export the core client and instead have our own implementations. Models and schemas are currently untouched, but as stated before, the services are touched and updated. The main advantage of this approach is not requiring manual intervention whenever a change in the schema is made.
Base client changes
The original services are generated as classes, where each method is a static method.
As an example, to place an order, one would call it as:
OrdersService.createOrderByQuantity A service in the case of openapi schema is a
tagGroup, while the method is the
Both the client configuration (url, headers) and the request operation (how to perform the
API call) are hardcoded/static as well, meaning we perform a
with the same client configuration and logic to perform the request. This is not ideal since
we might want to provide different implementations or even maybe have multiple clients with
different configurations (using multiple clients for multiple accounts/users for example).
Since we don't have duplicated
operationIds, we have agreed to provide all these methods at
the base level of the client, so we do not want to address them via tagGroups.
To accomodate for points above, these changes are made in all generated service files:
- Make service class abstract and add
clientas abstract protected consts.
- Added an
optionsobject parameter to every method. This object contains
accountIdwhich is used to override the
- Remove all
staticmodifiers from methods
- Update method to perform the request by providing the above defined
- Sporadic changes to accomodate to our custom core implementation, removing unnecessary
dependencies and renaming others (remove
- The way parameters are handled has been reworked.
queryParamsfields are united under
dataobject. This allows for named parameters and overall better usage for our users. This means our OpenAPI schema needs to make sure there are no duplicate names between these two fields on an endpoint but it's a compromise we've agreed to do.
This allows us to do a bit of a hackish behaviour, and have our client provide the
client implementations, and then add all methods of the services to it. It makes all
operationIds present at the base level. We then create an interface on the client that inherits
from all the services, putting the
operationIds types in the client class. This is known as
mixin in Typescript.
See for more info: https://www.typescriptlang.org/docs/handbook/mixins.html#alternative-pattern
To make this automatic creation of the client a reality, we emply a strategy where a
base_client.ts is used to generate the final
client.ts. The main stages are:
base_client.tsis copied as a base to
- all services are imported and all their methods are added to the LuneClient class.
- LuneClient interface is created that extends all services so we have type information in our client.
index.tsis appended to
client.tsso as to have the models and schemas also at the base level. This makes it possible to use
lune.models.Mass. This can only be achieved since we don't mix names between models, schemas and operations.
We currently use Axios to perform the operations and define our config via an interface dedicated to hold it. The core implementation might be improved a bit, being still highly based on the original core library since it was quite good already.
How to use
example-usage folder for a basic usage of the library in both TS and JS. But mostly, all you
need is to add it as a dependency, create a new client and use as normally :)
Also, you can check
README_DIST.md for the README present on the npm package, example usage should be
explicit there so not repeating it here.
If you want to rebuild everything locally from the remote OpenAPI schema (as performed on CI):
docker-compose -f docker-compose-ci.yml run update_from_remote_schema
If you want to make sure the build is succeeding but don't want to rebuild the client from the
remote schema nor update with possible changes in the base client generator (as performed on CI):
docker-compose -f docker-compose-ci.yml run build_from_source
For local builds, you can use a more lightweight version to rebuild the schema from the remote
that doesn't make sure dependencies are installed and skips linting:
docker-compose -f docker-compose.yml run local_model_rebuild
If you want to get hands on inside the container, you can get in and use the make commands as much
as you want. To get a shell inside it, just do:
docker-compose -f docker-compose.yml run client
Once inside, you have all Makefile commands at your disposal. Feel free to explore the
see all available. Here are some examples:
- Fully rebuild from the remote openAPI schema:
- Build with current source code:
Publishing is currently done automatically whenever changes happen in
package.json. This is usually not done manually. Rather we use one of the provided github workflows to increase the version (major, minor or patch). This will create a PR that bumps the version in package.json and the PR then just needs to be approved and merged.
Commonly you want to publish the client after making changes to the API (adding a new endpoint) etc. After the docs are updateд with your changes, this should automatically create a PR in lune-ts repo. After merging that PR, you should be able to proceed with the rest of the publishing workflow described above:
- Rich types on data (Big on Mass instead of string for example)
- Add advanced functionalities like HMAC verification etc.
- Improve core code. It is still highly similar to original code and I believe a better approach could be made.
- Verify example usage and improve README.