Skip to content

Commit a960ee3

Browse files
feat: add bidirectional-adapter
1 parent 4ac8b31 commit a960ee3

File tree

16 files changed

+544
-13
lines changed

16 files changed

+544
-13
lines changed

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,9 @@
3737
"**/.husky": true,
3838
"**/.yarn": true,
3939
"**/.turbo": true
40+
},
41+
"sonarlint.connectedMode.project": {
42+
"connectionId": "voiceflow",
43+
"projectKey": "voiceflow_oss"
4044
}
4145
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createConfig } from '@voiceflow/dependency-cruiser-config';
2+
3+
export default createConfig();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ISC License
2+
3+
Copyright 2021 Voiceflow Inc.
4+
5+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Bi-Directional Adapter
2+
3+
[![npm version](https://img.shields.io/npm/v/bidirectional-adapter.svg?style=flat-square)](https://www.npmjs.com/package/bidirectional-adapter)
4+
[![npm downloads](https://img.shields.io/npm/dm/bidirectional-adapter.svg?style=flat-square)](https://www.npmjs.com/package/bidirectional-adapter)
5+
6+
Factory to create bi-directional adapters that can convert between two distinct data structures.
7+
Using adapters helps to decouple systems which share common data structures and may need to alter them
8+
independently without introducing conflicts in each other.
9+
10+
## Why?
11+
12+
You have multiple services which each operate on a single shared resource but want to represent the data
13+
differently in each service or want to isolate each service from data structure changes required by the other.
14+
15+
With `bidirectional-adapter` you can define a simple entity which can be used to transform data between two formats.
16+
17+
```ts
18+
const adapter = createAdapter<string, number>(
19+
(stringValue) => parseInt(stringValue, 10),
20+
(numericValue) => String(numericValue)
21+
);
22+
```
23+
24+
## Example
25+
26+
```ts
27+
import axios from 'axios';
28+
import createAdapter from '@voiceflow/bidirectional-adapter';
29+
30+
const accountAdapter = createAdapter(
31+
(dbAccount) => ({
32+
id: dbAccount.user_id,
33+
name: `${dbAccount.user.firstName} ${dbAccount.user.lastName}`,
34+
email: dbAccount.user.email,
35+
}),
36+
(appAccount) => ({
37+
user_id: appAcount.id,
38+
user: {
39+
firstName: appAccount.name.split(' ')[0],
40+
lastName: appAccount.name.split(' ')[1],
41+
},
42+
email: appAccount.email,
43+
})
44+
);
45+
46+
const fetchAccount = (userID) => {
47+
// state is in the shape of propertyTwo
48+
// do reducer stuff here
49+
return axios.get(`/account/${userID}`).then((res) => accountAdapter.fromDB(res.data));
50+
};
51+
52+
const updateAccount = (account) => {
53+
// state is in the shape of propertyTwo
54+
// do reducer stuff here
55+
return axios.post(`/account/${userID}`, accountAdapter.toDB(account));
56+
};
57+
```
58+
59+
## Smart Adapter Example
60+
61+
```ts
62+
import { createSmartMultiAdapter } from '@voiceflow/bidirectional-adapter';
63+
64+
interface DBModel {
65+
x: number;
66+
a: number;
67+
b: string;
68+
c1: boolean;
69+
}
70+
71+
interface Model {
72+
x: number;
73+
ab: string;
74+
c2: boolean;
75+
}
76+
77+
type KeyMap = [['a' | 'b', 'ab'], ['c1', 'c2']];
78+
79+
const adapter = createSmartMultiAdapter<DBModel, Model, [], [], KeyMap>(
80+
() => ({}) as any,
81+
() => ({}) as any
82+
);
83+
84+
adapter.fromDB({ a: 1, b: 'a', c1: false, x: 1 }); // Model
85+
adapter.fromDB({ a: 1, b: 'a', c1: false }); // Pick<Model, 'ab' | 'c2'>
86+
adapter.fromDB({ b: 'a', c1: false }); // Pick<Model, 'c2'>
87+
adapter.fromDB({ x: 1 }); // Pick<Model, 'x1'>
88+
adapter.fromDB({}); // EmptyObject
89+
90+
adapter.mapFromDB([{ a: 1, b: 'a', c1: false, x: 1 }]); // Model[]
91+
adapter.mapFromDB([{ a: 1, b: 'a', c1: false }]); // Pick<Model, 'ab' | 'c2'>[]
92+
adapter.mapFromDB([{ b: 'a', c1: false }]); // Pick<Model, 'c2'>[]
93+
adapter.mapFromDB([{ x: 1 }]); // Pick<Model, 'x1'>[]
94+
adapter.mapFromDB([{}]); // EmptyObject[]
95+
96+
adapter.toDB({ ab: '1', c2: false, x: 1 }); // DBModel
97+
adapter.toDB({ ab: '1', x: 1 }); // Pick<DBModel, 'a' | 'b' | 'x'>
98+
adapter.toDB({ c2: false }); // Pick<Model, 'c2'>
99+
adapter.toDB({ x: 1 }); // Pick<Model, 'x1'>
100+
adapter.toDB({}); // EmptyObject
101+
102+
adapter.mapToDB([{ ab: '1', c2: false, x: 1 }]); // DBModel
103+
adapter.mapToDB([{ ab: '1', x: 1 }]); // Pick<DBModel, 'a' | 'b' | 'x'>
104+
adapter.mapToDB([{ c2: false }]); // Pick<Model, 'c2'>
105+
adapter.mapToDB([{ x: 1 }]); // Pick<Model, 'x1'>
106+
adapter.mapToDB([{}]); // EmptyObject
107+
```
108+
109+
## Installation
110+
111+
To use `bidirectional-adapter`, install it as a dependency:
112+
113+
```bash
114+
# If you use npm:
115+
npm install @voiceflow/bidirectional-adapter
116+
117+
# Or if you use Yarn:
118+
yarn add @voiceflow/bidirectional-adapter
119+
```
120+
121+
This assumes that you’re using a package manager such as [npm](http://npmjs.com/).
122+
123+
## License
124+
125+
[ISC](LICENSE.md)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "@voiceflow/bidirectional-adapter",
3+
"version": "2.0.0",
4+
"description": "bi-directional adapter factory, used to decouple systems across shared data structures",
5+
"keywords": [
6+
"adapter",
7+
"data",
8+
"model",
9+
"voiceflow"
10+
],
11+
"homepage": "https://github.com/voiceflow/oss/tree/master/libs/bidirectional-adapter#readme",
12+
"bugs": {
13+
"url": "https://github.com/voiceflow/oss/issues"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/voiceflow/oss.git"
18+
},
19+
"license": "ISC",
20+
"author": "Tyler Han, Ben Teichman",
21+
"type": "module",
22+
"main": "build/index.js",
23+
"types": "build/index.d.ts",
24+
"files": [
25+
"build"
26+
],
27+
"scripts": {
28+
"build": "yarn g:turbo run build:cmd --filter=@voiceflow/bidirectional-adapter...",
29+
"build:cmd": "yarn g:build:pkg",
30+
"clean": "yarn g:rimraf build",
31+
"lint": "yarn g:run-p -c lint:eslint lint:prettier",
32+
"lint:eslint": "yarn g:eslint",
33+
"lint:fix": "yarn g:run-p -c \"lint:eslint --fix\" \"lint:prettier --write\"",
34+
"lint:prettier": "yarn g:prettier --check",
35+
"tdd": "yarn g:vitest",
36+
"test": "yarn g:run-p -c test:dependencies test:types test:unit",
37+
"test:dependencies": "yarn g:depcruise",
38+
"test:types": "yarn g:tsc --noEmit",
39+
"test:unit": "yarn g:vitest run --coverage"
40+
},
41+
"engines": {
42+
"node": "20"
43+
},
44+
"volta": {
45+
"extends": "../../package.json"
46+
}
47+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
sonar.projectName=oss-bidirectional-adapter
2+
sonar.sources=src/
3+
sonar.tests=src/
4+
sonar.exclusions=src/**/*.test.ts
5+
sonar.test.inclusions=src/**/*.test.ts
6+
sonar.cpd.exclusions=src/**/*.test.ts
7+
sonar.typescript.tsconfigPath=tsconfig.json
8+
sonar.javascript.lcov.reportPaths=sonar/coverage/lcov.info
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { createMultiAdapter } from './adapter';
4+
5+
describe('bidirectional-adapter', () => {
6+
it('converts between two simple data types', () => {
7+
const stringNumberAdapter = createMultiAdapter<string, number>(
8+
(stringValue) => parseInt(stringValue, 10),
9+
(numericValue) => String(numericValue)
10+
);
11+
12+
expect(stringNumberAdapter.fromDB('123')).to.eq(123);
13+
expect(stringNumberAdapter.toDB(123)).to.eq('123');
14+
});
15+
});

0 commit comments

Comments
 (0)