Skip to content
This repository was archived by the owner on Nov 26, 2025. It is now read-only.

Commit 2d2b4ca

Browse files
authored
feat: implement smart adapters (CT-928) (#10)
* feat: implement smart adapters (CT-928) * fix: build * fix: ci * fix: ci
1 parent f25eccf commit 2d2b4ca

10 files changed

Lines changed: 1615 additions & 975 deletions

.depcheckrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"fixpack",
2020
"istanbul",
2121
"husky",
22+
"source-map-support",
2223
"lint-staged"
2324
]
2425
}

package.json

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,34 @@
1212
}
1313
},
1414
"devDependencies": {
15-
"@commitlint/cli": "^11.0.0",
16-
"@istanbuljs/nyc-config-typescript": "^1.0.1",
17-
"@types/chai": "^4.2.15",
18-
"@types/mocha": "^9.0.0",
19-
"@voiceflow/commitlint-config": "1.1.0",
20-
"@voiceflow/eslint-config": "3.4.0",
21-
"@voiceflow/git-branch-check": "1.2.0",
22-
"@voiceflow/prettier-config": "1.0.5",
23-
"@voiceflow/tsconfig": "1.1.0",
15+
"@commitlint/cli": "11.0.0",
16+
"@istanbuljs/nyc-config-typescript": "^1.0.2",
17+
"@types/chai": "^4.3.3",
18+
"@types/mocha": "^9.1.1",
19+
"@voiceflow/commitlint-config": "2.0.0",
20+
"@voiceflow/eslint-config": "6.1.0",
21+
"@voiceflow/git-branch-check": "1.4.0",
22+
"@voiceflow/prettier-config": "1.2.1",
23+
"@voiceflow/tsconfig": "1.4.8",
2424
"@zerollup/ts-transform-paths": "^1.7.18",
25-
"chai": "^4.3.0",
26-
"commitizen": "^4.2.3",
25+
"chai": "^4.3.6",
26+
"commitizen": "^4.2.5",
2727
"cz-conventional-changelog": "^3.3.0",
28-
"depcheck": "^1.4.2",
29-
"eslint": "^7.20.0",
28+
"depcheck": "^1.4.3",
29+
"eslint": "^8.23.0",
3030
"eslint-output": "^3.0.1",
3131
"fixpack": "^4.0.0",
32-
"husky": "^4.3.8",
32+
"husky": "^8.0.1",
3333
"istanbul": "^0.4.5",
34-
"lint-staged": "^10.5.4",
35-
"mocha": "^8.3.0",
34+
"lint-staged": "^13.0.3",
35+
"mocha": "^10.0.0",
3636
"nyc": "^15.1.0",
37-
"prettier": "^2.2.1",
38-
"source-map-support": "^0.5.19",
39-
"ts-mocha": "^8.0.0",
40-
"ts-node": "^9.1.1",
41-
"ttypescript": "^1.5.12",
42-
"typescript": "^4.4.2"
37+
"prettier": "^2.7.1",
38+
"source-map-support": "^0.5.21",
39+
"ts-mocha": "^10.0.0",
40+
"ts-node": "^10.9.1",
41+
"ttypescript": "^1.5.13",
42+
"typescript": "^4.8.2"
4343
},
4444
"files": [
4545
"build/"
@@ -52,14 +52,17 @@
5252
"voiceflow"
5353
],
5454
"license": "ISC",
55-
"main": "build/index.js",
55+
"main": "build/cjs/index.js",
56+
"module": "build/esm/index.js",
5657
"prettier": "@voiceflow/prettier-config",
5758
"repository": {
5859
"type": "git",
5960
"url": "git+https://github.com/voiceflow/bidirectional-adapter.git"
6061
},
6162
"scripts": {
62-
"build": "yarn clean && ttsc --project ./tsconfig.build.json --declaration",
63+
"build": "yarn clean && yarn build:cjs && yarn build:esm",
64+
"build:cjs": "ttsc --build tsconfig.build.json",
65+
"build:esm": "ttsc --build tsconfig.esm.json",
6366
"clean": "rimraf build",
6467
"commit": "cz",
6568
"eslint-output": "eslint-output",
@@ -75,5 +78,5 @@
7578
"test:single": "NODE_ENV=test ts-mocha --paths --config ./config/test/.mocharc.yml",
7679
"test:unit": "NODE_ENV=test nyc --report-dir=nyc_coverage_unit ts-mocha --paths --config ./config/test/.mocharc.yml 'test/**/*.unit.ts'"
7780
},
78-
"types": "build/index.d.ts"
79-
}
81+
"types": "build/cjs/index.d.ts"
82+
}

src/adapter.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
export interface FromDB<DBModel, Model, FromDBArgs extends any[] = []> {
2+
(dbModel: DBModel, ...args: FromDBArgs): Model;
3+
}
4+
5+
export interface SmartFromDB<DBModel, Model, FromDBArgs extends any[] = []> extends FromDB<DBModel, Model, FromDBArgs> {
6+
<PartialDBModel extends Partial<DBModel>>(dbModel: PartialDBModel, ...args: FromDBArgs): Pick<Model, Extract<keyof Model, keyof PartialDBModel>>;
7+
}
8+
9+
export interface MapFromDB<DBModel, Model, FromDBArgs extends any[] = []> {
10+
(dbModels: DBModel[], ...args: FromDBArgs): Model[];
11+
}
12+
13+
export interface SmartMapFromDB<DBModel, Model, FromDBArgs extends any[] = []> extends MapFromDB<DBModel, Model, FromDBArgs> {
14+
<PartialDBModel extends Partial<DBModel>>(dbModels: PartialDBModel[], ...args: FromDBArgs): Pick<
15+
Model,
16+
Extract<keyof Model, keyof PartialDBModel>
17+
>[];
18+
}
19+
20+
export interface ToDB<DBModel, Model, ToDBArgs extends any[] = []> {
21+
(model: Model, ...args: ToDBArgs): DBModel;
22+
}
23+
24+
export interface SmartToDB<DBModel, Model, ToDBArgs extends any[] = []> extends ToDB<DBModel, Model, ToDBArgs> {
25+
<PartialModel extends Partial<Model>>(model: PartialModel, ...args: ToDBArgs): Pick<DBModel, Extract<keyof DBModel, keyof PartialModel>>;
26+
}
27+
28+
export interface MapToDB<DBModel, Model, ToDBArgs extends any[] = []> {
29+
(models: Model[], ...args: ToDBArgs): DBModel[];
30+
}
31+
32+
export interface SmartMapToDB<DBModel, Model, ToDBArgs extends any[] = []> extends MapToDB<DBModel, Model, ToDBArgs> {
33+
<PartialModel extends Partial<Model>>(diagrams: PartialModel[], ...args: ToDBArgs): Pick<DBModel, Extract<keyof DBModel, keyof PartialModel>>[];
34+
}
35+
36+
export interface SimpleAdapter<DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []> {
37+
toDB: ToDB<DBModel, Model, ToDBArgs>;
38+
fromDB: FromDB<DBModel, Model, FromDBArgs>;
39+
}
40+
41+
export interface SmartSimpleAdapter<DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []> {
42+
toDB: SmartToDB<DBModel, Model, ToDBArgs>;
43+
fromDB: SmartFromDB<DBModel, Model, FromDBArgs>;
44+
}
45+
46+
export interface MultiAdapter<DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>
47+
extends SimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs> {
48+
mapToDB: MapToDB<DBModel, Model, ToDBArgs>;
49+
mapFromDB: MapFromDB<DBModel, Model, FromDBArgs>;
50+
}
51+
52+
export interface SmartMultiAdapter<DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>
53+
extends SmartSimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs> {
54+
mapToDB: SmartMapToDB<DBModel, Model, ToDBArgs>;
55+
mapFromDB: SmartMapFromDB<DBModel, Model, FromDBArgs>;
56+
}
57+
58+
export interface AdapterOptions {
59+
debug?: (message: string, model: unknown) => void;
60+
}
61+
62+
export interface AnyMultiAdapter extends MultiAdapter<any, any> {}
63+
64+
export interface AnySimpleAdapter extends SimpleAdapter<any, any> {}
65+
66+
const toDBFactory =
67+
<ToDBArgs extends any[] = []>(toDB: (model: any, ...args: ToDBArgs) => any, { debug }: AdapterOptions = {}) =>
68+
(model: any, ...args: ToDBArgs): any => {
69+
debug?.('adapter called with value from the store', model);
70+
71+
const result = toDB(model, ...args);
72+
73+
debug?.('converted store value to', result);
74+
75+
return result;
76+
};
77+
78+
const fromDBFactory =
79+
<FromDBArgs extends any[] = []>(fromDB: (dbModel: any, ...args: FromDBArgs) => any, { debug }: AdapterOptions = {}) =>
80+
(dbModel: any, ...args: FromDBArgs): any => {
81+
debug?.('adapter called with value from DB', dbModel);
82+
83+
const result = fromDB(dbModel, ...args);
84+
85+
debug?.('converted DB value to', result);
86+
87+
return result;
88+
};
89+
90+
const mapToDBFactory =
91+
<ToDBArgs extends any[] = []>(toDB: (model: any, ...args: ToDBArgs) => any, { debug }: AdapterOptions = {}) =>
92+
(models: any[], ...args: ToDBArgs): any[] => {
93+
debug?.('adapter called with values from store', models);
94+
95+
const result = models.map((model) => toDB(model, ...args));
96+
97+
debug?.('converted store values to', result);
98+
99+
return result;
100+
};
101+
102+
const mapFromDBFactory =
103+
<FromDBArgs extends any[] = []>(fromDB: (dbModel: any, ...args: FromDBArgs) => any, { debug }: AdapterOptions = {}) =>
104+
(dbModels: any[], ...args: FromDBArgs): any[] => {
105+
debug?.('adapter called with values from DB', dbModels);
106+
107+
const result = dbModels.map((dbModel) => fromDB(dbModel, ...args));
108+
109+
debug?.('converted DB values to', result);
110+
111+
return result;
112+
};
113+
114+
export const createSimpleAdapter = <DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>(
115+
fromDB: (dbModel: DBModel, ...args: FromDBArgs) => Model,
116+
toDB: (model: Model, ...args: ToDBArgs) => DBModel,
117+
options: AdapterOptions = {}
118+
): SimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs> => ({
119+
toDB: toDBFactory(toDB, options),
120+
121+
fromDB: fromDBFactory(fromDB, options),
122+
});
123+
124+
export const createSmartSimpleAdapter = <DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>(
125+
fromDB: (dbModel: Partial<DBModel>, ...args: FromDBArgs) => Partial<Model>,
126+
toDB: (model: Partial<Model>, ...args: ToDBArgs) => Partial<DBModel>,
127+
options: AdapterOptions = {}
128+
// eslint-disable-next-line sonarjs/no-identical-functions
129+
): SmartSimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs> => ({
130+
toDB: toDBFactory(toDB, options),
131+
132+
fromDB: fromDBFactory(fromDB, options),
133+
});
134+
135+
export const createMultiAdapter = <DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>(
136+
fromDB: (dbModel: DBModel, ...args: FromDBArgs) => Model,
137+
toDB: (model: Model, ...args: ToDBArgs) => DBModel,
138+
options: AdapterOptions = {}
139+
): MultiAdapter<DBModel, Model, FromDBArgs, ToDBArgs> => ({
140+
...createSimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs>(fromDB, toDB, options),
141+
142+
mapToDB: mapToDBFactory(toDB, options),
143+
144+
mapFromDB: mapFromDBFactory(fromDB, options),
145+
});
146+
147+
export const createSmartMultiAdapter = <DBModel, Model, FromDBArgs extends any[] = [], ToDBArgs extends any[] = []>(
148+
fromDB: (dbModel: Partial<DBModel>, ...args: FromDBArgs) => Partial<Model>,
149+
toDB: (model: Partial<Model>, ...args: ToDBArgs) => Partial<DBModel>,
150+
options: AdapterOptions = {}
151+
): MultiAdapter<DBModel, Model, FromDBArgs, ToDBArgs> => ({
152+
...createSmartSimpleAdapter<DBModel, Model, FromDBArgs, ToDBArgs>(fromDB, toDB, options),
153+
154+
mapToDB: mapToDBFactory(toDB, options),
155+
156+
mapFromDB: mapFromDBFactory(fromDB, options),
157+
});

src/identity.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createMultiAdapter, createSimpleAdapter } from './adapter';
2+
3+
export type Identity = <Model>(model: Model) => Model;
4+
export type IdentityMulti = <Model>(models: Model[]) => Model[];
5+
6+
export interface Simple {
7+
toDB: Identity;
8+
fromDB: Identity;
9+
}
10+
11+
export interface Multi extends Simple {
12+
mapToDB: IdentityMulti;
13+
mapFromDB: IdentityMulti;
14+
}
15+
16+
export const transformer: Identity = (value) => value;
17+
18+
export const multi: Multi = createMultiAdapter<any, any>(transformer, transformer);
19+
20+
export const simple: Simple = createSimpleAdapter<any, any>(transformer, transformer);
21+
22+
export const createMulti = <Model>() => createMultiAdapter<Model, Model>(transformer, transformer);
23+
24+
export const createSimple = <Model>() => createSimpleAdapter<Model, Model>(transformer, transformer);

src/index.ts

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,5 @@
1-
const identity = <T>(value: T): T => value;
2-
3-
export class AdapterNotImplementedError extends Error {
4-
constructor() {
5-
super('adapter not implemented');
6-
}
7-
}
8-
9-
export interface AdapterOptions {
10-
debug?: (message: string, value: any) => void;
11-
}
12-
13-
export type Adapter<I, A extends any[], O> = (value: I, ...args: A) => O;
14-
15-
export interface BidirectionalAdapter<I, O, T extends any[], R extends any[]> {
16-
fromDB: Adapter<I, T, O>;
17-
toDB: Adapter<O, R, I>;
18-
}
19-
20-
export type AnyBidirectionalAdapter = BidirectionalAdapter<any, any, any[], any[]>;
21-
22-
export type BidirectionalMultiAdapter<I, O, T extends any[], R extends any[]> = BidirectionalAdapter<I, O, T, R> & {
23-
mapFromDB: Adapter<I[], T, O[]>;
24-
mapToDB: Adapter<O[], R, I[]>;
25-
};
26-
27-
export type AnyBidirectionalMultiAdapter = BidirectionalMultiAdapter<any, any, any[], any[]>;
28-
29-
export const createSimpleAdapter = <I, O, T extends any[] = [], R extends any[] = []>(
30-
fromDB: Adapter<I, T, O>,
31-
toDB: Adapter<O, R, I>,
32-
{ debug }: AdapterOptions = {}
33-
): BidirectionalAdapter<I, O, T, R> => ({
34-
fromDB: (dbValue, ...args) => {
35-
debug?.('adapter called with value from DB', dbValue);
36-
37-
const result = fromDB(dbValue, ...args);
38-
39-
debug?.('converted DB value to', result);
40-
41-
return result;
42-
},
43-
toDB: (appValue, ...args) => {
44-
debug?.('adapter called with value from the store', appValue);
45-
46-
const result = toDB(appValue, ...args);
47-
48-
debug?.('converted store value to', result);
49-
50-
return result;
51-
},
52-
});
53-
54-
export const createAdapter = <I, O, T extends any[] = [], R extends any[] = []>(
55-
fromDB: Adapter<I, T, O>,
56-
toDB: Adapter<O, R, I>,
57-
options: AdapterOptions = {}
58-
): BidirectionalMultiAdapter<I, O, T, R> => ({
59-
...createSimpleAdapter<I, O, T, R>(fromDB, toDB, options),
60-
mapFromDB: (dbValues, ...args) => {
61-
options.debug?.('adapter called with values from DB', dbValues);
62-
63-
const result = dbValues.map((dbValue) => fromDB(dbValue, ...args));
64-
65-
options.debug?.('converted DB values to', result);
66-
67-
return result;
68-
},
69-
mapToDB: (appValues, ...args) => {
70-
options.debug?.('adapter called with values from store', appValues);
71-
72-
const result = appValues.map((appValue) => toDB(appValue, ...args));
73-
74-
options.debug?.('converted store values to', result);
75-
76-
return result;
77-
},
78-
});
79-
80-
export default createAdapter;
81-
82-
export const identityAdapter: {
83-
toDB: <T>(value: T) => T;
84-
fromDB: <T>(value: T) => T;
85-
} = createSimpleAdapter<any, any>(identity, identity);
1+
export * from './adapter';
2+
// eslint-disable-next-line no-restricted-exports
3+
export { createMultiAdapter as default } from './adapter';
4+
export * as identityAdapter from './identity';
5+
export * as notImplementedAdapter from './notImplemented';

src/notImplemented.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createMultiAdapter, createSimpleAdapter } from './adapter';
2+
3+
export class NotImplementedError extends Error {
4+
constructor() {
5+
super('adapter not implemented');
6+
}
7+
}
8+
9+
export const transformer = (): never => {
10+
throw new NotImplementedError();
11+
};
12+
13+
export const multi = createMultiAdapter(transformer, transformer);
14+
export const simple = createSimpleAdapter(transformer, transformer);

test/constants.unit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { expect } from 'chai';
22

3-
import { createAdapter } from '@/.';
3+
import { createMultiAdapter } from '@/.';
44

55
describe('bidirectional-adapter', () => {
66
it('converts between two simple data types', () => {
7-
const stringNumberAdapter = createAdapter<string, number>(
7+
const stringNumberAdapter = createMultiAdapter<string, number>(
88
(stringValue) => parseInt(stringValue, 10),
99
(numericValue) => String(numericValue)
1010
);

0 commit comments

Comments
 (0)