Skip to content

Commit

Permalink
Merge pull request #7 from nobrainr/fix/allow-unsubscribe-interceptors
Browse files Browse the repository at this point in the history
Fix: Allow to unsubscribe interceptors
  • Loading branch information
emyann authored Nov 15, 2018
2 parents 326060b + 737b49f commit de2beac
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 15 deletions.
168 changes: 163 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
# axios-morphism

> An Axios plugin to transform requests and response data on-the-fly based on a schema.
This package is built upon [`Morphism`](https://www.npmjs.com/package/morphism). Access the documentation [here](https://github.com/nobrainr/morphism).

---

- [axios-morphism](#axios-morphism)
- [Getting started](#getting-started)
- [Installation](#installation)
- [Example](#example)
- [Usage](#usage)
- [Schema](#schema)
- [Interceptors Configuration](#interceptors-configuration)
- [Example:](#example)
- [Axios-Morphism Configuration](#axios-morphism-configuration)
- [Apply Configurations](#apply-configurations)
- [Remove Configurations](#remove-configurations)
- [Combine Configurations](#combine-configurations)
- [License](#license)

## Getting started

### Installation

```sh
npm install --save axios
npm install --save axios # Axios is defined as a PeerDependency in axios-morphism
npm install --save axios-morphism
```

### Example

```typescript
import axios from "axios";
import { apply, AxiosMorphismConfiguration } from './axios-morphism';
import axios from 'axios';
import { apply, AxiosMorphismConfiguration } from 'axios-morphism';

const peopleSchema = {
name: 'name',
Expand All @@ -24,7 +44,10 @@ const peopleSchema = {
const configuration: AxiosMorphismConfiguration = {
url: 'https://swapi.co/api/',
interceptors: {
responses: [{ matcher: '/people/:id', schema: peopleSchema }],
responses: [
{ matcher: '/people', schema: peopleSchema, dataSelector: 'results' },
{ matcher: '/people/:id', schema: peopleSchema }
],
requests: []
}
};
Expand All @@ -33,12 +56,147 @@ const client = axios.create({baseURL: 'https://swapi.co/api/'});
apply(client, configuration);

await client.get('/people/1');
// {
// name: "Luke Skywalker"
// height: "172"
// weight: "77"
// }

await client.get('/people');
// [
// {
// name: "Luke Skywalker"
// height: "172"
// weight: "77"
// },....
// ]
```

## Usage

### Schema

Define a schema corresponding to the shape you expect to have after the transformation has been applied.

[Read About Morphism's Schema Capabilities](https://github.com/nobrainr/morphism#1-the-schema)

```typescript
const peopleSchema = {
name: 'name',
height: 'height',
weight: ({ mass }) => `${mass} KG`
};
```

### Interceptors Configuration

Create your configurations to be applied on Axios requests or responses.

#### Example:

```typescript
const configuration: AxiosMorphismConfiguration = {
url: 'https://swapi.co/api/',
interceptors: {
responses: [
{ matcher: '/people', schema: peopleSchema, dataSelector: 'results' },
{ matcher: /\/people\/([^\/]+?)(?:\/)?$/i, schema: peopleSchema }, // matches /people/:id
{
matcher: (response: AxiosResponse) => response.config.method === 'POST', // matches every responses obtained using a POST
schema: peopleSchema,
dataSelector: 'results'
}
],
requests: []
}
};
```

#### Axios-Morphism Configuration

| Property | Type | Description | Example |
| -------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| url | ```string``` | Base URL to listen on | `https://swapi.co/api` |
| interceptors | ```{ responses: [], requests: []};``` | List of Responses and Requests Interceptors Configuration to register against Axios |
| interceptors.responses[].matcher | ```string | RegExp | Function``` | Matcher used to detect on which response to apply the transformer | - `'people/:id'` <br> - `/people$/i` <br> - `(response: AxiosResponse) => response.config.method === 'POST'` |
| interceptors.requests[].matcher | ```string | RegExp | Function``` | Matcher used to detect on which request to apply the transformer | - `'planets/:id'` <br> - `/planets$/i` <br> - `(request: AxiosRequestConfig) => request.url.includes('planets')` |
| interceptors.requests[].schema interceptors.responses[].schema | ```Schema | StrictSchema``` | A schema is an object-preserving map from one data structure to another. | [Morphism Schema Examples](https://github.com/nobrainr/morphism#schema-example) |
| interceptors.requests[].dataSelector interceptors.responses[].dataSelector | ```string``` | A selector to access the data in the Axios returned data | With this Axios Response: ```{ data: { results: [] }}```. Pick the data with ```{ dataSelector: 'results' }``` |



### Apply Configurations

Apply your interceptors on your axios instance and there you go!

```typescript
import { apply } from 'axios-morphism';

const configuration: AxiosMorphismConfiguration = {
url: 'https://swapi.co/api/',
interceptors: {
responses: [
{ matcher: '/people', schema: peopleSchema, dataSelector: 'results' },
{ matcher: /\/people\/([^\/]+?)(?:\/)?$/i, schema: peopleSchema } // Will match /people/:id
],
requests: []
}
};

const client = axios.create({ baseURL: 'https://swapi.co/api/' });
apply(client, configuration);

// Start making requests to see you data transformed
await client.get('/people');
await client.get('/people/1');
```

### Remove Configurations
Use the `unsubscribe` method returned from the `apply` function to opt-out from the interceptors

```typescript
const configuration: AxiosMorphismConfiguration = {...};

const axiosMorphism = apply(client, config);
axiosMorphism.unsubscribe(); // Remove all registered interceptors
```

### Combine Configurations

`axios-morphism` provides the `combine` function in order to help you merge multiple configurations under a `baseURL`.

```typescript
import { apply, combine, AxiosMorphismConfiguration } from 'axios-morphism';

const peopleMorphism: AxiosMorphismConfiguration = {
url: '/people',
interceptors: {
requests: [],
responses: [
{ matcher: '/', schema: { name: 'name', url: 'url' }, dataSelector: 'results' },
{ matcher: '/:id', schema: { name: 'name', url: 'url' }, dataSelector: 'results' }
]
}
};
const planetMorphism: AxiosMorphismConfiguration = {
url: '/planets',
interceptors: {
requests: [],
responses: [
{ matcher: '/', schema: { name: 'name', url: 'url' }, dataSelector: 'results' },
{ matcher: '/:id', schema: { name: 'name', url: 'url' }, dataSelector: 'results' }
]
}
};

const client = axios.create({ baseURL: 'https://swapi.co/api/' });
apply(client, combine('https://swapi.co/api/', peopleMorphism, planetMorphism));

// Start making requests to see you data transformed
await client.get('/people');
await client.get('/planets/1');
```

## License
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"name": "axios-morphism",
"description": "",
"version": "1.0.0",
"description": "An Axios plugin to transform requests and response data on-the-fly based on a schema.",
"homepage": "https://github.com/nobrainr/axios-morphism",
"repository": {
"type": "git",
"url": "git+https://github.com/nobrainr/axios-morphism.git"
},
"version": "1.0.5",
"main": "./dist/axios-morphism.js",
"types": "./dist/axios-morphism.d.ts",
"files": [
Expand Down
30 changes: 30 additions & 0 deletions src/axios-morphism.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,5 +442,35 @@ describe('Axios Morphism', () => {
expect(onPostSpy).toHaveBeenCalledWith(expectedPerson);
});
});

describe('Unsubscribe Interceptors', () => {
let client: AxiosInstance;
let mock: MockAdapter;
const baseURL = 'https://swapi.co/api';
beforeEach(() => {
client = axios.create();
mock = new MockAdapter(client);

// People API
mock.onGet(`${baseURL}/people`).reply(200, { results: [mockPeople] });
});
afterEach(() => {
mock.reset();
});

it('should unsubscribe registered interceptors', async () => {
const config: AxiosMorphismConfiguration = {
url: baseURL,
interceptors: {
requests: [],
responses: [{ matcher: '/people', schema: peopleSchema, dataSelector: 'results' }]
}
};
const axiosMorphism = apply(client, config);
axiosMorphism.unsubscribe();
const people = await client.get(`${baseURL}/people`);
expect(people.data.results).toEqual([mockPeople]);
});
});
});
});
24 changes: 16 additions & 8 deletions src/axios-morphism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,24 @@ function createInterceptors(configuration: AxiosMorphismConfiguration) {
}

export function apply(client: AxiosInstance, ...configurations: AxiosMorphismConfiguration[]) {
configurations.forEach(configuration => {
const subscriptions = configurations.map(configuration => {
const { responses, requests } = createInterceptors(configuration);
responses.forEach(interceptor => {
client.interceptors.response.use(interceptor);
});
requests.forEach(interceptor => {
client.interceptors.request.use(interceptor);
});
const responseIds = responses.map(interceptor => client.interceptors.response.use(interceptor));
const requestIds = requests.map(interceptor => client.interceptors.request.use(interceptor));
return { responsesSubscriptions: responseIds, requestsSubscriptions: requestIds };
});
return client;
return {
unsubscribe: () => {
subscriptions.forEach(subscription => {
subscription.responsesSubscriptions.forEach(id => {
client.interceptors.response.eject(id);
});
subscription.requestsSubscriptions.forEach(id => {
client.interceptors.request.eject(id);
});
});
}
};
}

export function combine(baseURL: string, ...configurations: AxiosMorphismConfiguration[]) {
Expand Down

0 comments on commit de2beac

Please sign in to comment.