@bumped-inc/bumped-api-client v0.74.2
@bumped-inc/bumped-api-client
Client to fetch data from the Bumped API
About
The API for the Bumped-API-Client has had breaking changes, relatively often. As we pin down our patterns, these have become and will continue to become less frequent.
The primary places the Bumped-API-Client are used are in bumped-web, and some light usage within bumped.com. It is likely that it will be used in some form in bumped-app. It could also be used for end-to-end tests.
Guidelines for development
The most important aspect to prioritize of the Bumped-API-Client is internal consistency. As a user of the client, there should be the least amount of surprises possible.
Updating generated types for GraphQL API
If there is a change to the GraphQL API Schema, then schema references must be updated via
$ yarn add --dev @bumped-inc/graphql-schema-artifacts
And then it is recommended to run
$ yarn run relay
Which will use the new schema to generate relevant types. One may first need to make changes to GraphQL queries or mutations before any change is apparent.
Updating generated code and types for REST API
If there is a change to the REST API, then the OpenAPI spec must be updated via
$ cd external/api-specs
$ git pull
$ cd ../..
$ yarn run update-api-yml
$ yarn run generate-rest-client
This should update the git submodule within external/api-specs
to the most
recently-published version, then copy the OpenAPI spec to an appropriate local
path, then use that to generate new code and types via
openapi-generator
.
Input types
All input types are plain objects. They may contain non-plain properties which may be converted into something more usable before it goes across the wire, but that is intentionally obfuscated from the caller.
If an object can be empty, undefined
is also accepted as an input, for
convenience's sake.
When defining an input interface
type, the name is typically the PascalCase
version of the function name with the Input
suffix. e.g. getOffer
->
GetOfferInput
.
If there are no properties that would be useful for an input interface
type
(including optional ones), then undefined
should explicitly be used and an
interface
should not be unnecessarily defined.
As time progresses, a new data requirement may be added to an existing request.
If more input data is required, that is typically denoted as a new property on
the Input
interface. If it is optional, then this is generally not considered
a breaking change.
Output types
Output types fall into one of two categories, with no exceptions:
void
(represented asundefined
at runtime), which implies that there is no useful information to retrieve from the network upon successful completion of the request. Whether the request resolves or rejects is the only needed information.- A plain object, which has one or more properties that can provide useful information to the caller. This should never be an array, as that is less extensible and not future-proof.
When defining an input interface
type, the name is typically the PascalCase
version of the function name with the Payload
suffix. e.g. getOffer
->
GetOfferPayload
, but may be a simpler type such as Offer
itself.
New data requirements may be added to an existing request. These should be
either a new property on the Payload
interface or changes to the internal
types of said interface. Adding new data in this way should almost never be a
breaking change.
Pagination
Pagination is accounted for by the addition of two input properties and an extra output property.
input
readonly limit?: number;
Specifies the maximum amount of items per page. Do not alter this between calls when using the same cursor.
input
readonly after?: ID<'ListDocumentsCursor'>;
Specifies the cursor of the last page that was seen. When requesting the first page, this should be
undefined
. All subsequent pages have that starting cursor, which can be seen like a bookmark.output
readonly nextPage: ID<'ListDocumentsCursor'> | null;
Specifies the cursor that can be provided as the
after
property for the next call. Ifnull
, then there are no more pages to return.
More, pagination-specific properties may be provided, on a per-request basis, such as the total count of items or facet information about a search query. Those may be useful and informative, but only the above three properties are required in order to support pagination.
Naming
For exported functions which will result in one or more network requests, use
standard action tense for function naming, without unnecessary suffixes such as
Async
or Promise
or Request
.
When querying data in a non-mutative way, starting with the word get
is a safe
choice.
When querying paginated data, instead prefer to start with the word list
.
Stipulation: if querying a list of items that are not paginated, do not
start with the word list
.
When sending a mutative request, start with a relevant action verb such as
add
, remove
, create
, or update
. Whatever word is chosen, it should be
obvious that it is making a change and ideally obvious about what it intends to
do.
Documentation
Documentation should be put on all public exports, including type exports.
Public documentation generally exists in one of three areas:
On an exported function:
JSDoc should be used, and speak of the function in the active tense, e.g. "Subscribes to a particular notification."
Because the input type and output type should be fully-documented interfaces themselves, they can often go unmentioned within the function documentation.
On an input type:
Each property should be documented and provide a reasonable description for what that property does. If it is optional, either be explicit about what the
undefined
case means or explicitly call out the default value using the@default
syntax.If a property type is a disjoint union, it is likely that each possible type within the union needs full documentation.
If a property type is an object itself, each of its properties should be documented in standard JSDoc fashion.
On an output type:
Follow the same direction for input types, but the phrasing should be directed at someone who will be using and observing the output type as something to read from rather than to construct and provide.
Be explicit about what
undefined
ornull
means if they are possibilities.
Pitfalls to avoid
Do not manually define generated interfaces. If you are trying to define an object type that comes from an API, that should be generated by an automated tool and not maintained manually. (Note: this does not apply to enum definitions: see the Enums section below.)
A corrolary to this is that it is perfectly fine to define type aliases of generated types, including reaching into a generated type to retrieve one that would not otherwise be easily-accessible.
Enums
Do not re-use enum
s that are generated either from the REST client or the
GraphQL generated code, as these tend to have subpar documentation and are
typically not in the best form to use from external usage.
Instead, define an enum
with other types (in a types.ts
file, likely), where
each key is a legibile PascalCase name. Do not define this as a const enum
, as
that can cause problems in some environments. The values can match the values of
the generated enum, though.
Also, for each defined enum, define a function that starts with is
to allow
for easy verification.
For example, for an ActivityType
enum, the following should be declared
immediately after definition:
/**
* Returns whether `value` is an `ActivityType`
*
* @param value The value to check, which should probably be a string
*/
export const isActivityType = createIsEnumValue(ActivityType);
With this, when mapping a response payload to a return type, the following pattern can be used:
function mapResponse({ activityType }) {
return {
activityType: isActivityType(activityType)
? activityType
: ActivityType.SomeSaneDefault,
};
}
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago