Skip to content

Commit

Permalink
Merge pull request #206 from infinum/feature/network
Browse files Browse the repository at this point in the history
Network implementation
  • Loading branch information
DarkoKukovec committed Oct 3, 2020
2 parents cd77fe2 + a756669 commit 2676ede
Show file tree
Hide file tree
Showing 94 changed files with 4,506 additions and 192 deletions.
215 changes: 215 additions & 0 deletions docs/assets/datx-network-interceptors.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/jsonapi/jsonapi-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ cache: CachingStrategy;
maxCacheAge: number; // seconds
```

Options for caching of requests. This can be overriden on a collection or request level.
Options for caching of requests. This can be overridden on a collection or request level.
2 changes: 1 addition & 1 deletion docs/jsonapi/jsonapi-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Replace the response model with a different model. Used to replace a model while

## data

An model or an array of model received from the API
An model or an array of models received from the API

## error

Expand Down
32 changes: 32 additions & 0 deletions docs/mixins/network-mixin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
id: network-mixin
title: Network Mixin
---

If you're using an API for your application, you can install `datx-network` to take the full advantage of the `datx` library:

```bash
npm install --save datx datx-network mobx
```

**Note** If you're using the [JSON API specification](https://jsonapi.org/), check out [datx-jsonapi](./jsonapi-mixin) instead.

## Polyfilling

The lib makes use of the following features that are not yet available everywhere. Based on your browser support, you might want to polyfill them:

- [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
- [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
- [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)

## Documentation

- [BaseRequest](../network/base-request)
- [Response](../network/response)
- [operators](../network/operators)
- [caching](../network/caching)
- [interceptors](../network/interceptors)
- [parse/serialize](../network/parse-serialize)
- [fetching](../network/fetching)
- [TypeScript Interfaces](../network/typescript-interfaces)
57 changes: 57 additions & 0 deletions docs/network/base-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
id: base-request
title: BaseRequest
---

BaseRequest is the main class to create a new request pipeline.

# constructor

```typescript
constructor(baseUrl: string): BaseRequest;
```

The constructor has one mandatory argument and that's the base url.

# pipe

```typescript
pipe<
TNewModel extends PureModel | Array<PureModel> = TModel,
TNewParams extends object = TParams
>(...operators: Array<IPipeOperator>): BaseRequest<TNewModel, TNewParams>;
```
The pipe method is used to configure the requests. It will always create a clone of the current base request and modify the clone.
To see what's possible to configure, check out the [operators](./operators) page.
This method can receive two types in the generic. The first one is the expected return type of the `fetch` method. This can be either a model type or an array of models. The second type is a `Record` defining which params are expected in the fetch method - this should match the `setUrl` placeholders that weren't defined in the `params` operator.
# clone
```typescript
clone<TNewModel extends PureModel = TModel, TNewParams extends object = TParams>(
BaseRequestConstructor?: typeof BaseRequest,
): BaseRequest<TNewModel, TNewParams>;
```
This method clones the current BaseRequest class instance. Since it's possible to use custom BaseRequest classes that extend the original one, the clone method will make sure the correct class is used. If needed, you can also pass a different BaseClass implementation.
# fetch
```typescript
fetch(params?: TParams): Promise<Response<TModel>>;
```
The fetch method will make the API request and either return a [`Response`](./response) with the `data` property if successful or throw a `Response` with an `error` property.
# useHook
```typescript
useHook(
params?: TParams,
options?: { suspense: boolean },
): [Response<TModel> | null, boolean, string | Error | null];
```
This method is a React hook. It can receive two arguments - the first one is the params object, and the second one is a hook options object. The only option supported right now is a `suspense` flag that will work with the [React suspense for data fetching](https://reactjs.org/docs/concurrent-mode-suspense.html).
The return value is an array consisting of three items: The first one is a `Response` that will contain either the `data` or `error` property. The second value is a boolean loading flag, and the third one is an error value that will either be `null`, a string or an `Error` object.
23 changes: 23 additions & 0 deletions docs/network/caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
id: caching
title: Caching
---

The library can use multiple caching strategies in order to optimize your network communication. The caching works only for `GET` requests and it can be controlled by using the [`cache` operator](./operators).

Supported caching strategies are:

```typescript
enum CachingStrategy {
NetworkOnly = 1, // Ignore cache
NetworkFirst = 2, // Fallback to cache only on network error
StaleWhileRevalidate = 3, // Use cache and update it in background
CacheOnly = 4, // Fail if nothing in cache
CacheFirst = 5, // Use cache if available
StaleAndUpdate = 6, // Use cache and update response once network is complete
}
```

The default caching strategy in the browser is `CachingStrategy.CacheFirst` and on the server it's `CachingStrategy.NetworkOnly`.

Besides the caching strategy, you can also set the `maxAge` value, which is a number of seconds a response will be cached for. The default maxAge is `Infinity`.
6 changes: 6 additions & 0 deletions docs/network/fetching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: fetching
title: Fetching TODO
---

# TODO
25 changes: 25 additions & 0 deletions docs/network/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
id: getting-started
title: Getting started with DatX Network
---

# Getting started

Note: `datx-network` has a peer dependency to `mobx@^4.2.0` or `mobx@^5.5.0`, so don't forget to install the latest MobX version:

```bash
npm install --save datx-network mobx
```

## Polyfilling

The lib makes use of the following features that are not yet available everywhere. Based on your browser support, you might want to polyfill them:

- [Symbol.for](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
- [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
- [Array.prototype.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
- [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)

[How to add the polyfills](https://datx.dev/docs/troubleshooting/known-issues#the-library-doesnt-work-in-internet-explorer-11).
Note: Fetch API is not included in the polyfills mentioned in the Troubleshooting page. Instead, you need to add it as a separate library. If you don't have any special requirements (like server-side rendering), you can use the [window.fetch polyfill](https://github.com/github/fetch#installation).
57 changes: 57 additions & 0 deletions docs/network/interceptors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
id: interceptors
title: Interceptors TODO
---

The library is using a concept of interceptors to handle use cases like authentication or other flows. This is conceptually similar to [Angular interceptors](https://angular.io/api/common/http/HttpInterceptor) or [Express middleware](https://expressjs.com/en/guide/using-middleware.html).

THe interceptors are responsible for most of the flow once the fetch method is called - including caching and doing the actual API request.

The interceptors receive the request object ([`IFetchOptions`](./typescript-ionterfaces#ifetchoptions)) and an optional `next` function representing the next interceptor in the chain. The return value of every interceptor should be a promise of the response object.

Only the bottom interceptor won't get the next function and that means that it will be responsible for making the actual API call.

## Call order

If we execute the following code:

```typescript
request
.pipe(addInterceptor(interceptorA), addInterceptor(interceptorB), addInterceptor(interceptorC))
.fetch();
```

the call stack will look like this:

![Interceptor stacking diagram](../assets/datx-network-interceptors.svg)

Basically, the last added interceptor will be the first one called, and the last one getting the response back.

## Builtin interceptors

The library contains two builtin interceptors that try to make lib usage as simple as possible. However, in order to make things as flexible as possible, you can either completely remove or replace any of the interceptors with your implementation.

Bellow is the description of the builtin interceptors and what to do if you want to replace them.

**Note:** If you replace one of the builtin interceptors, make sure you don't use any of the operators mentioned bellow, as they will restore the original interceptor.

### Fetch

The fetch interceptor is responsible for making the request. It has three configurable options, each one can be modified by using one of the operators.

Options:

- fetch reference - this needs to be defined as `window.fetch` or any other compatible function, like the one from the `isomorphic-fetch` library. In the browser, the reference will be automatically set to `window.fetch` if it exists. If you're running the lib outside of the main browser process or the browser might not support the Fetch API, then you can set your own fetch implementation by using the [`fetchReference` operator](./operators#fetchreference).
- serializer - this function prepares the request body for the API call. This might be something like transforming a model snapshot to a format supported by the API. The function can be defined by using the [`serializer` operator](./operators#serializer)
- parser - this function prepares the API response for the [`Response`](./response) initialization. This might be e.g. because the response objects need to be modified or because the response is nested in a way that's not compatible with the `Response` class.

### Cache

The cache interceptor is responsible for caching of the network request. This implementation will cache only the `GET` requests. The interceptor supports two options described bellow, and they can be changed by using the [`cache` operator](./operators#cache).

Options:

- The default caching strategy in the browser is `CachingStrategy.CacheFirst` and on the server it's `CachingStrategy.NetworkOnly`.
- Besides the caching strategy, you can also set the `maxAge` value, which is a number of seconds a response will be cached for. The default maxAge is `Infinity`.

You can find more about the caching and configuration details on the [caching page](./caching).
169 changes: 169 additions & 0 deletions docs/network/operators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
id: operators
title: Operators
---

The operators are used to configure the [base request](./base-request).

## setUrl

```typescript
function setUrl(url: string, type: IType | typeof PureModel = PureModel);
```

Set the endpoint that should be loaded and the model that should be initialized. The url can contain placeholders that will be filled by using the `params` operator or as a fetch argument.
The url will be appended to the base url. An example with placeholders: `/articles/{articleId}`.

## addInterceptor

```typescript
function addInterceptor(interceptor: IInterceptor, name?: string);
```

The library is using a concept of interceptors to handle use cases like authentication or other flows. This is conceptually similar to [Angular interceptors](https://angular.io/api/common/http/HttpInterceptor) or [Express middleware](https://expressjs.com/en/guide/using-middleware.html).

The interceptors can have an optional name so it's easier to manipulate them in later stages. The default name, if none given, will be the given function name.

To find more about interceptors, check out the [interceptors](./interceptors) page.

## upsertInterceptor

```typescript
function upsertInterceptor(interceptor: IInterceptor, name?: string);
```

Replace the interceptor with the given name. The new interceptor will be placed in the same place in the order as the old interceptor.

## removeInterceptor

```typescript
function removeInterceptor(name: string);
```

Remove the interceptor with the given name.

## cache

```typescript
function cache(strategy: CachingStrategy, maxAge?: number);
```

The library supports multiple caching strategies. Only the GET request can be cached and you can configure the behavior by using the `cache` operator. The operator receives a strategy and the max age.
The default caching strategy in the browser is `CachingStrategy.CacheFirst` and on the server it's `CachingStrategy.NetworkOnly`. The default maxAge is `Infinity`.

To find out more about the caching strategies, check out the [caching](./caching) page. To implement your custom caching strategy, check out the [interceptors](./interceptors) page about how to replace builtin interceptors.

_Note:_ Used only with the [built in cache interceptor](./interceptors#cache).

## method

```typescript
function method(method: HttpMethod);
```

Set the HTTP method that should be used for the request.

## body

```typescript
function body(body: any, bodyType?: BodyType);
```

To send a body with your request, use the `body` operator. As a first argument, it receives the payload to be send. If the second argument is not set, the library will try to discover what type should be used. The body type is used for the payload serialization and `Content-Type` header value.

## query

```typescript
function query(name: string, value: string | Array<string> | object);
```

With this operator you can add query parameters to your url. The parameters can also be objects and arrays, and the `paramArrayType` operator can be used to set the way the query params will be serialized.

## header

```typescript
function header(name: string, value: string);
```

Set the HTTP headers that should be used for the request. Calling it multiple times with the same name will override the value.

## params

```typescript
function params(name: string, value: string): (pipeline: BaseRequest) => void;
function params(params: Record<string, string>): (pipeline: BaseRequest) => void;
```

Set the parameters that will be set in the url. Calling it multiple times with the same name will override the value.

## fetchReference

```typescript
function fetchReference(fetchReference: typeof fetch);
```

Set the reference to the fetch method. When running in the browser, this will default to `window.fetch`, while there will be no default for the server. To make the network work across server and client, you can use a library like `isomorphic-fetch`.

_Note:_ Used only with the [built in fetch interceptor](./interceptors#fetch).

## encodeQueryString

```typescript
function encodeQueryString(encodeQueryString: boolean);
```

By default, all query strings added by using the `query` operator will be url encoded. If needed, you can disable the encoding with this operator.

## paramArrayType

```typescript
function paramArrayType(paramArrayType: ParamArrayType);
```

Since the `query` operator params can be complex objects and there are multiple ways to serialize them, this option defines a desired way to do it. The default value is `ParamArrayType.ParamArray` and the possible options are:

```typescript
export enum ParamArrayType {
MultipleParams, // filter[a]=1&filter[a]=2
CommaSeparated, // filter[a]=1,2
ParamArray, // filter[a][]=1&filter[a][]=2
}
```

## serializer

```typescript
function serializer(serialize: (request: IFetchOptions) => IFetchOptions);
```

Prepare the body of the request for sending. The default serializer just passes the unmodified data argument.

_Note:_ Used only with the [built in fetch interceptor](./interceptors#fetch).

## parser

```typescript
function parser(parse: (data: object, response: IResponseObject) => object);
```

Parse the API response before data initialization. The function also receives other data that could be useful for parsing. An example use case for the parser is if all your API response is wrapped in a `data` object or something similar.

_Note:_ Used only with the [built in fetch interceptor](./interceptors#fetch).

## collection

```typescript
function collection(collection?: PureCollection);
```

Link the base request to a specific DatX collection. By using this, all the models received by the API response will be added to the specified collection. This is also required in order to use references inside of your models.

## Custom operators

You can also create custom operators that work with the base request. The operator needs to follow the `IPipeOperator` typing.

```typescript
type IPipeOperator = (request: BaseRequest) => void;
```

For now, no options of the base request are publicly exposed, but they might be in future. If you have some specific requests, please open an issue with the suggestion.
Loading

0 comments on commit 2676ede

Please sign in to comment.