Skip to content

Commit

Permalink
docs: add typedocs to core classes and improve contribution guide (#1156
Browse files Browse the repository at this point in the history
)
  • Loading branch information
BugGambit authored Sep 25, 2024
1 parent f556717 commit afdb62f
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 8 deletions.
68 changes: 68 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,71 @@ git push && git push --tags

This will make a commit with the updated `package.json`, create a new git tag, and publish to npm.
Make sure you are logged in to npm, talk to a maintainer.

## Code overview

### HTTP Client

The core of the SDK is the HTTP client. The HTTP client is divided into multiple layers:

1. [BasicHttpClient](./packages/core/src/httpClient/basicHttpClient.ts)
2. [RetryableHttpClient](./packages/core/src/httpClient/retryableHttpClient.ts)
3. [CDFHttpClient](./packages/core/src/httpClient/cdfHttpClient.ts)

See each file for a description of what they do.

### Pagination

We have multiple utilities to easy pagination handling. The first entrypoint is [cursorBasedEndpoint](./packages/core/src/baseResourceApi.ts) which adds a `next()` function on the response to fetch the next page of result. Then we use [makeAutoPaginationMethods](./packages/core/src/autoPagination.ts) to add the following methods:

- autoPagingToArray
```ts
const assets = await client.assets.list().autoPagingToArray({ limit: 100 });
```
- autoPagingEach
```ts
for await (const asset of client.assets.list().autoPagingEach({ limit: Infinity })) {
// ...
}
```

### Date parser

Some API responses includes DateTime responses represented as UNIX timestamps. We have utility class to automatically translate the number response into a Javascript [Date instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date).

See the [DateParser class](./packages/core/src/dateParser.ts).

### Metadata

We offer users to get hold of the HTTP response status code and headers through the [MetadataMap class](./packages/core/src/metadata.ts).

### Core utilities

- [promiseAllWithData](./packages/core/src/utils.ts)
- [promiseCache](./packages/core/src/utils.ts)
- [topologicalSort](./packages/core/src/graphUtils.ts)
- [RevertableArraySorter](./packages/core/src/revertableArraySorter.ts)

### Cognite Clients

There is a Cognite Client per SDK package:
- [Core](./packages/core/src/baseCogniteClient.ts)
- [Stable](./packages/stable/src/cogniteClient.ts)
- [Beta](./packages/beta/src/cogniteClient.ts)
- [Alpha](./packages/alpha/src/cogniteClient.ts)
- [Playground](./packages/playground/src/cogniteClientPlayground.ts)
- [Template](./packages/template/src/cogniteClient.ts)

The Core one is the base, meaning the others extends from it.


#### Authentication

The authentication logic lives in the [core BaseCogniteClient](./packages/core/src/baseCogniteClient.ts).
The client constructor offer the field `oidcTokenProvider` (formely called `getToken`) where the SDK user will provide a valid access token.

The SDK will call this method when:

- The user calls `authenticate` on the client.
- The SDK receives a 401 from the API.
When multiple requests receives a 401, then only a single call to `oidcTokenProvider` will be invoked. All requests will wait for `oidcTokenProvider` to resolve/reject. If it's resolved, then all the requests will retry before returning the response to the SDK caller. However, if the resolved access token matches the original access token, then no retry will be performed.
17 changes: 16 additions & 1 deletion packages/core/src/autoPagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ import type {
ListResponse,
} from './types';

/** @hidden */
/**
* The `makeAutoPaginationMethods` function generates methods for automatically
* paginating through API responses. This is particularly useful when dealing with
* APIs that return large sets of data across multiple pages, allowing you to
* seamlessly iterate through all available data without manually handling pagination.
*
* This function typically creates methods that:
* - Fetch the next page of results based on the current page's metadata.
* - Aggregate results from multiple pages into a single iterable or array.
*
* @param apiClient - The API client instance used to make requests.
* @param initialRequest - The initial request object containing the parameters for the first page.
* @returns An iterable or async iterable that yields items from the paginated response.
*
* @hidden
*/
export function makeAutoPaginationMethods<T>(
firstPagePromise: Promise<ListResponse<T[]>>
) {
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/baseCogniteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export default class BaseCogniteClient {
private readonly http: CDFHttpClient;
private readonly metadata: MetadataMap;
private readonly getOidcToken: () => Promise<string | undefined>;
private readonly noAuthMode?: boolean;
readonly project: string;
private retryValidator: RetryValidator;

Expand Down Expand Up @@ -163,9 +162,6 @@ export default class BaseCogniteClient {

if (token === undefined) return token;

if (this.noAuthMode) {
return token;
}
const bearer = bearerString(token);
this.httpClient.setDefaultHeader(AUTHORIZATION_HEADER, bearer);
return token;
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/httpClient/basicHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ import { isJson } from '../utils';
import { HttpError, type HttpErrorData } from './httpError';
import type { HttpHeaders } from './httpHeaders';

/**
* The `BasicHttpClient` class provides a simplified HTTP client for making
* requests to a server. It abstracts away the underlying HTTP client and
* the complexities of handling different response types and error handling,
* making it easier to interact with HTTP APIs.
*
* This class includes static methods for:
* - Validating HTTP status codes.
* - Throwing custom error responses.
* - Handling JSON, text, and array buffer responses.
* - Selecting the appropriate response handler based on the expected response type.
*
* The `BasicHttpClient` is designed to be used in scenarios where you need to
* make HTTP requests and handle responses in a consistent and predictable manner.
* It ensures that responses are correctly parsed and that errors are properly
* propagated, allowing for robust error handling.
*
* @remarks
* This class relies on the `cross-fetch` library for making HTTP requests,
* and it uses custom error and header types defined in the project.
*
* @see {@link HttpError}
* @see {@link HttpHeaders}
* @see {@link HttpResponseType}
*/
export class BasicHttpClient {
private static validateStatusCode(status: number) {
return status >= 200 && status < 300;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/httpClient/retryValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ export const createUniversalRetryValidator =
if (retryCount >= maxRetries) {
return false;
}
// Always retry http 429 too many requests
// Always retry http 429 (Too Many Requests)
if (response.status === 429) {
return true;
}
// By default, retry requests with HTTP verbs that are meant to be idempotent
const httpMethodsToRetry = ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT'];
const isRetryableHttpMethod =
httpMethodsToRetry.indexOf(request.method.toUpperCase()) !== -1;
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/httpClient/retryableHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,32 @@ import {
} from './basicHttpClient';
import { MAX_RETRY_ATTEMPTS, type RetryValidator } from './retryValidator';

/**
* The `RetryableHttpClient` class extends the functionality of a basic HTTP client
* by adding automatic retry logic for failed requests. This is particularly useful
* for improving the resilience of applications that depend on unreliable network
* connections or external services that may experience intermittent failures.
*
* This class includes methods for:
* - Configuring retry policies, including the number of retries and delay between retries.
* - Making HTTP requests with automatic retries on failure.
* - Handling different types of HTTP responses and errors.
*
* The `RetryableHttpClient` is designed to be used in scenarios where you need to
* make HTTP requests that may occasionally fail and need to be retried. It ensures
* that transient errors do not cause the application to fail, improving overall
* reliability and user experience.
*
* @remarks
* This class builds on top of a basic HTTP client and adds retry logic. It uses
* custom error handling and response parsing methods to ensure that retries are
* only attempted for transient errors and not for permanent failures.
*
* @see {@link BasicHttpClient}
* @see {@link HttpError}
* @see {@link HttpHeaders}
* @see {@link HttpResponseType}
*/
export class RetryableHttpClient extends BasicHttpClient {
private static calculateRetryDelayInMs(retryCount: number) {
const INITIAL_RETRY_DELAY_IN_MS = 250;
Expand Down
20 changes: 19 additions & 1 deletion packages/core/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@ export interface Metadata {
headers: { [key: string]: string };
}

/** @hidden */
/**
* When making API requests, the response includes metadata such as the HTTP status code and headers.
* Often, the SDK user doesn't care about this data, and therefore the happy path
* of the SDK excludes this data by default.
*
* However, the SDK allows the user to look-up the metadata if they need it.
*
* @example
* ```ts
* const response = await client.assets.list();
* const metadata = client.getMetadata(response);
* ```
*
* @remarks
* We utilize [WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
* to avoid memory leaks.
*
* @hidden
*/
export class MetadataMap {
private map: WeakMap<object, Metadata>;
constructor() {
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/revertableArraySorter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
// Copyright 2020 Cognite AS

/**
* The `RevertableArraySorter` class provides functionality to sort an array
* while maintaining the ability to revert to the original order. This is useful
* in scenarios where you need to temporarily sort data for display or processing
* but want to easily revert back to the original order without having to store
* a separate copy of the array.
*
* The motivation for this class was to handle creation of Assets.
* If you want to create many Assets, then it's important that parent Assets
* are created before child assets or parent and child assets
* are contained in the same API call. The SDK caller only need to provide a
* long array of Assets and the SDK will sort them in the correct order.
* However, the caller expects the response array to match the input array order.
* So when we sort the input array, we need to remember it's original order.
*/
export class RevertableArraySorter<InputType> {
private originalIndexMap: Map<InputType, number> = new Map();
private sortedArray?: InputType[];
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ export async function promiseAllWithData<RequestType, ResponseType>(

/**
* Resolves Promises sequentially
*
* @hidden
*/
/** @hidden */
export async function promiseEachInSequence<RequestType, ResponseType>(
inputs: RequestType[],
promiser: (input: RequestType) => Promise<ResponseType>
Expand Down

0 comments on commit afdb62f

Please sign in to comment.