Skip to content

Commit 3b3e5b8

Browse files
committed
feat: Add support for inferred destination types
1 parent d3e9f63 commit 3b3e5b8

File tree

2 files changed

+83
-47
lines changed

2 files changed

+83
-47
lines changed

src/morphism.spec.ts

+35-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Morphism from './morphism';
1+
import Morphism, { StrictSchema } from './morphism';
22

33
class User {
44
firstName: string;
@@ -29,7 +29,28 @@ class User {
2929
}
3030

3131
describe('Morphism', () => {
32-
const dataToCrunch = [
32+
interface MockData {
33+
firstName: string;
34+
lastName: string;
35+
age: number;
36+
address: {
37+
streetAddress: string;
38+
city: string;
39+
state: string;
40+
postalCode: string;
41+
};
42+
phoneNumber: [
43+
{
44+
type: string;
45+
number: string;
46+
},
47+
{
48+
type: string;
49+
number: string;
50+
}
51+
];
52+
}
53+
const dataToCrunch: MockData[] = [
3354
{
3455
firstName: 'John',
3556
lastName: 'Smith',
@@ -200,7 +221,7 @@ describe('Morphism', () => {
200221
describe('Function Predicate', function() {
201222
it('should support es6 destructuring as function predicate', function() {
202223
let schema = {
203-
target: ({ source }: any) => source
224+
target: ({ source }: { source: string }) => source
204225
};
205226
let mock = {
206227
source: 'value'
@@ -210,6 +231,7 @@ describe('Morphism', () => {
210231
};
211232
let result = Morphism(schema, mock);
212233
expect(result).toEqual(expected);
234+
expect(result.target).toEqual(expected.target);
213235
});
214236

215237
it('should support nesting mapping', function() {
@@ -348,11 +370,18 @@ describe('Morphism', () => {
348370
});
349371

350372
it('should pass the object value to the function when no path is specified', function() {
351-
let schema = {
373+
interface D {
374+
firstName: string;
375+
lastName: string;
376+
city: string;
377+
status: string;
378+
}
379+
380+
let schema: StrictSchema<D, MockData> = {
352381
firstName: 'firstName',
353382
lastName: 'lastName',
354-
city: 'address.city',
355-
status: (o: any) => o.phoneNumber[0].type
383+
city: { path: 'address.city', fn: prop => prop },
384+
status: o => o.phoneNumber[0].type
356385
};
357386

358387
let desiredResult = {

src/morphism.ts

+48-41
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
*/
44
import { isString, get, isFunction, zipObject, isUndefined, assignInWith, aggregator, isObject } from './helpers';
55

6-
export function isActionSelector(value: any): value is ActionSelector {
7-
return isObject(value);
8-
}
96
/**
107
* A Function invoked per iteration
118
* @param {} iteratee The current element to transform
@@ -31,8 +28,8 @@ export function isActionSelector(value: any): value is ActionSelector {
3128
* ```
3229
*
3330
*/
34-
export interface ActionFunction<D, S> {
35-
(iteratee: S, source: S[], target: D): any;
31+
export interface ActionFunction<D, S, R> {
32+
(iteratee: S, source: S[], target: D): R;
3633
}
3734
/**
3835
* A String path that indicates where to find the property in the source input
@@ -100,7 +97,7 @@ export type ActionAggregator = string[];
10097
*```
10198
*
10299
*/
103-
export type ActionSelector = { path: string | string[]; fn: (fieldValue: any, items: any[]) => any };
100+
export type ActionSelector<Source, R> = { path: string | string[]; fn: (fieldValue: any, items: Source[]) => R };
104101

105102
/**
106103
* A structure-preserving object from a source data towards a target data.
@@ -134,23 +131,25 @@ export type ActionSelector = { path: string | string[]; fn: (fieldValue: any, it
134131
* ```
135132
*/
136133

137-
export type StrictSchema<Target = {}, Source = any> = {
134+
export type StrictSchema<Target = any, Source = any> = {
138135
/** `destinationProperty` is the name of the property of the target object you want to produce */
139136
[destinationProperty in keyof Target]:
140137
| ActionString<Source>
141-
| ActionFunction<Target, Source>
138+
| ActionFunction<Target, Source, Target[destinationProperty]>
142139
| ActionAggregator
143-
| ActionSelector
140+
| ActionSelector<Source, Target[destinationProperty]>
144141
};
145-
export type Schema<Target = {}, Source = any> = {
142+
export type Schema<Target = any, Source = any> = {
146143
/** `destinationProperty` is the name of the property of the target object you want to produce */
147144
[destinationProperty in keyof Target]?:
148145
| ActionString<Source>
149-
| ActionFunction<Target, Source>
146+
| ActionFunction<Target, Source, Target[destinationProperty]>
150147
| ActionAggregator
151-
| ActionSelector
148+
| ActionSelector<Source, Target[destinationProperty]>
152149
};
153-
150+
export function isActionSelector<S, R>(value: any): value is ActionSelector<S, R> {
151+
return isObject(value);
152+
}
154153
/**
155154
* Low Level transformer function.
156155
* Take a plain object as input and transform its values using a specified schema.
@@ -218,11 +217,6 @@ interface Constructable<T> {
218217
new (...args: any[]): T;
219218
}
220219

221-
export interface Mapper<Target> {
222-
<Source>(source: Source[]): Target[];
223-
<Source>(source: Source): Target;
224-
}
225-
226220
function transformItems<T, TSchema extends Schema<T>>(schema: TSchema): Mapper<{ [P in keyof TSchema]: any }>;
227221
function transformItems<T, TSchema extends Schema<T>>(
228222
schema: TSchema,
@@ -266,6 +260,15 @@ function getSchemaForType<T>(type: Constructable<T>, baseSchema: Schema<T>): Sch
266260
return finalSchema;
267261
}
268262

263+
type SourceFromSchema<T> = T extends StrictSchema<unknown, infer U> | Schema<unknown, infer U> ? U : never;
264+
type DestinationFromSchema<T> = T extends StrictSchema<infer U> | Schema<infer U> ? U : never;
265+
266+
type ResultItem<TSchema extends Schema> = { [P in keyof TSchema]: DestinationFromSchema<TSchema>[P] };
267+
export interface Mapper<TSchema extends Schema | StrictSchema, TResult = ResultItem<TSchema>> {
268+
(data: Partial<SourceFromSchema<TSchema>>[]): TResult[];
269+
(data: Partial<SourceFromSchema<TSchema>>): TResult;
270+
}
271+
269272
/**
270273
* Currying function that either outputs a mapping function or the transformed data.
271274
*
@@ -288,28 +291,32 @@ function getSchemaForType<T>(type: Constructable<T>, baseSchema: Schema<T>): Sch
288291
* @param {} type
289292
*
290293
*/
291-
export function morphism<TSchema extends Schema<{}, Source>, Source>(
294+
export function morphism<TSchema extends Schema, Source extends object>(
292295
schema: TSchema,
293-
items: Source
294-
): Source extends any[] ? { [P in keyof TSchema]: any }[] : { [P in keyof TSchema]: any };
295-
// morphism({},{}) => {}
296-
// morphism({},[]) => Target[]
296+
data: Source
297+
): Source extends (infer _C)[] ? ResultItem<TSchema>[] : ResultItem<TSchema>;
297298

298-
export function morphism<TSchema extends Schema<any>>(schema: TSchema): Mapper<{ [P in keyof TSchema]: any }>; // morphism(TSchema) => mapper(S[]) => (keyof TSchema)[]
299-
export function morphism<Target>(schema: Schema<Target>): Mapper<Target>; // morphism<ITarget>({}) => Mapper<ITarget> => ITarget
300-
export function morphism<Target, Source>(
301-
schema: Schema<Target>,
299+
// morphism({}) => mapper(S) => T
300+
export function morphism<TSchema extends Schema>(schema: TSchema): Mapper<TSchema>;
301+
302+
// morphism({}, null, T) => mapper(S) => T
303+
export function morphism<TSchema extends Schema, TDestination>(
304+
schema: TSchema,
302305
items: null,
303-
type: Constructable<Target>
304-
): Mapper<Target>; // morphism({}, null, T) => mapper(S) => T
305-
export function morphism<Target, Source>(
306-
schema: Schema<Target>,
307-
items: Source[],
308-
type: Constructable<Target>
309-
): Target[]; // morphism({}, [], T) => T[]
310-
export function morphism<Target, Source>(schema: Schema<Target>, items: Source, type: Constructable<Target>): Target; // morphism({}, {}, T) => T
306+
type: Constructable<TDestination>
307+
): Mapper<TSchema, TDestination>;
311308

312-
export function morphism<Target, Source>(schema: Schema<Target>, items?: Source, type?: Constructable<Target>) {
309+
// morphism({}, {}, T) => T
310+
export function morphism<TSchema extends Schema, Target>(
311+
schema: TSchema,
312+
items: SourceFromSchema<TSchema>,
313+
type: Constructable<Target>
314+
): Target;
315+
export function morphism<Target, Source, TSchema extends Schema<Target, Source>>(
316+
schema: TSchema,
317+
items?: SourceFromSchema<TSchema>,
318+
type?: Constructable<Target>
319+
) {
313320
if (items === undefined && type === undefined) {
314321
return transformItems(schema);
315322
} else if (schema && items && type) {
@@ -336,15 +343,15 @@ export interface IMorphismRegistry {
336343
* @param schema Structure-preserving object from a source data towards a target data.
337344
*
338345
*/
339-
register<Target, TSchema extends Schema<Target>>(type: Constructable<Target>, schema?: TSchema): Mapper<Target>;
346+
register<Target, TSchema>(type: Constructable<Target>, schema?: TSchema): Mapper<TSchema, Target>;
340347
/**
341348
* Transform any input in the specified Class
342349
*
343350
* @param {Type} type Class Type of the ouput Data
344351
* @param {Object} data Input data to transform
345352
*
346353
*/
347-
map<Target>(type: Target): Mapper<Target>;
354+
map<Target>(type: Target): Mapper<Schema, Target>;
348355
map<Target, Source>(type: Constructable<Target>, data: Source[]): Target[];
349356
map<Target, Source>(type: Constructable<Target>, data: Source): Target;
350357
/**
@@ -353,15 +360,15 @@ export interface IMorphismRegistry {
353360
* @param {Type} type Class Type of the ouput Data
354361
*
355362
*/
356-
getMapper<Target>(type: Constructable<Target>): Mapper<Target>;
363+
getMapper<Target>(type: Constructable<Target>): Mapper<Schema, Target>;
357364
/**
358365
* Set a schema for a specific Class Type
359366
*
360367
* @param {Type} type Class Type of the ouput Data
361368
* @param {Schema} schema Class Type of the ouput Data
362369
*
363370
*/
364-
setMapper<Target, TSchema extends Schema<Target>>(type: Constructable<Target>, schema: TSchema): Mapper<Target>;
371+
setMapper<Target, TSchema extends Schema<Target>>(type: Constructable<Target>, schema: TSchema): Mapper<any, Target>;
365372
/**
366373
* Delete a registered schema associated to a Class
367374
*
@@ -405,7 +412,7 @@ export class MorphismRegistry implements IMorphismRegistry {
405412
* @param schema Structure-preserving object from a source data towards a target data.
406413
*
407414
*/
408-
register<Target, TSchema extends Schema<Target>>(type: Constructable<Target>, schema?: TSchema) {
415+
register<Target, TSchema>(type: Constructable<Target>, schema?: TSchema) {
409416
if (!type && !schema) {
410417
throw new Error('type paramater is required when register a mapping');
411418
} else if (this.exists(type)) {

0 commit comments

Comments
 (0)