From d9277f68462d8ee210f33fbac7b7a5a7f0dca0ad Mon Sep 17 00:00:00 2001 From: Matt Ringer Date: Mon, 14 May 2018 02:15:22 -0700 Subject: [PATCH] Initial compose functionality and basic tests --- dist/helper.d.ts | 14 ++++++ dist/helper.js | 26 +++++++++++ dist/helper.js.map | 2 +- src/helper.ts | 36 ++++++++++++++- test/unit/helper_spec.js | 99 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 174 insertions(+), 3 deletions(-) diff --git a/dist/helper.d.ts b/dist/helper.d.ts index 606f240..1b928fc 100644 --- a/dist/helper.d.ts +++ b/dist/helper.d.ts @@ -1,3 +1,17 @@ export declare const combineResolvers: (resolvers?: any[]) => any; export declare const and: (...conditions: any[]) => (resolver: any) => any; export declare const or: (...conditions: any[]) => (resolver: any) => (...query: any[]) => Promise<{}>; +export declare class Composable { + resolver: any; + /** + * + * @param resolver + * TODO: a Resolver type is probably needed, but outside the scope of this PR because it requires refactoring resolver.ts + */ + constructor(resFn: any, errFn: any); + /** + * + * @param resolvers + */ + compose(resolvers: {}): {}; +} diff --git a/dist/helper.js b/dist/helper.js index c8c136e..9d626d2 100644 --- a/dist/helper.js +++ b/dist/helper.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +const resolver_1 = require("./resolver"); const merge = require("deepmerge"); // Helper function to combine multiple resolver definition hashes into a single hash for consumption by Apollostack's graphql-server exports.combineResolvers = (resolvers = []) => resolvers @@ -27,4 +28,29 @@ exports.or = (...conditions) => resolver => (...query) => { attempt(0); }); }; +class Composable { + /** + * + * @param resolver + * TODO: a Resolver type is probably needed, but outside the scope of this PR because it requires refactoring resolver.ts + */ + constructor(resFn, errFn) { + this.resolver = resolver_1.createResolver(resFn, errFn); + } + /** + * + * @param resolvers + */ + compose(resolvers) { + const composed = {}; + Object.keys(resolvers).forEach(key => { + const resolver = resolvers[key]; + composed[key] = (resolver.resolve || resolver.error) + ? this.resolver.createResolver(resolver.resolve, resolver.error) + : this.resolver.createResolver(resolver); + }); + return composed; + } +} +exports.Composable = Composable; //# sourceMappingURL=helper.js.map \ No newline at end of file diff --git a/dist/helper.js.map b/dist/helper.js.map index 6602270..8963ff0 100644 --- a/dist/helper.js.map +++ b/dist/helper.js.map @@ -1 +1 @@ -{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":";;AAAA,mCAAmC;AAEnC,oIAAoI;AACvH,QAAA,gBAAgB,GAAG,CAAC,SAAS,GAAG,EAAE,EAAE,EAAE,CAAC,SAAS;KAC1D,MAAM,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE7D,wFAAwF;AACxF,yFAAyF;AAC5E,QAAA,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE;IAC/C,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,EAAE,QAAQ,CAAC,CAAA;AACd,CAAC,CAAA;AAED,wFAAwF;AACxF,wFAAwF;AAC3E,QAAA,EAAE,GAAG,CAAC,GAAG,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,EAAE,EAAE;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,CACpB,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC;aAC7C,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;aACzB,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,IAAG,CAAC,KAAK,KAAK;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBACvB,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QACP,OAAO,CAAC,CAAC,CAAC,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":";;AAAA,yCAA4C;AAC5C,mCAAmC;AAGnC,oIAAoI;AACvH,QAAA,gBAAgB,GAAG,CAAC,SAAS,GAAG,EAAE,EAAE,EAAE,CAAC,SAAS;KAC1D,MAAM,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE7D,wFAAwF;AACxF,yFAAyF;AAC5E,QAAA,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE;IAC/C,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,EAAE,QAAQ,CAAC,CAAA;AACd,CAAC,CAAA;AAED,wFAAwF;AACxF,wFAAwF;AAC3E,QAAA,EAAE,GAAG,CAAC,GAAG,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,EAAE,EAAE;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,CACpB,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC;aAC7C,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;aACzB,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,IAAG,CAAC,KAAK,KAAK;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBACvB,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QACP,OAAO,CAAC,CAAC,CAAC,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC,CAAA;AAED;IAGE;;;;OAIG;IACH,YAAY,KAAK,EAAE,KAAK;QACtB,IAAI,CAAC,QAAQ,GAAG,yBAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED;;;OAGG;IACI,OAAO,CAAE,SAAa;QAC3B,MAAM,QAAQ,GAAG,EAAE,CAAC;QAEpB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YAEhC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC;gBAClD,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC;gBAChE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;CAEF;AA9BD,gCA8BC"} \ No newline at end of file diff --git a/src/helper.ts b/src/helper.ts index c2a73c9..dd31faa 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -1,4 +1,6 @@ +import { createResolver } from "./resolver"; import * as merge from "deepmerge"; +import { isFunction } from "./util"; // Helper function to combine multiple resolver definition hashes into a single hash for consumption by Apollostack's graphql-server export const combineResolvers = (resolvers = []) => resolvers @@ -26,4 +28,36 @@ export const or = (...conditions) => resolver => (...query) => { }); attempt(0); }); -} \ No newline at end of file +} + +export class Composable { + resolver: any; // stricter types won't pass the compiler because createResolver is unexpected on Funciton. + + /** + * + * @param resolver + * TODO: a Resolver type is probably needed, but outside the scope of this PR because it requires refactoring resolver.ts + */ + constructor(resFn, errFn) { + this.resolver = createResolver(resFn, errFn); + } + + /** + * + * @param resolvers + */ + public compose( resolvers: {} ) { + const composed = {}; + + Object.keys(resolvers).forEach(key => { + const resolver = resolvers[key]; + + composed[key] = (resolver.resolve || resolver.error) + ? this.resolver.createResolver(resolver.resolve, resolver.error) + : this.resolver.createResolver(resolver); + }); + + return composed; + } + +} diff --git a/test/unit/helper_spec.js b/test/unit/helper_spec.js index 394f4f8..72b5ea0 100644 --- a/test/unit/helper_spec.js +++ b/test/unit/helper_spec.js @@ -2,9 +2,10 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import { - combineResolvers, and, or, + combineResolvers, and, or, compose, Composable } from '../../dist/helper'; import { createResolver } from '../../dist/resolver'; +import { resolveAll } from 'jspm-config'; describe('(unit) src/helper.js', () => { describe('combineResolvers', () => { @@ -174,4 +175,100 @@ describe('(unit) src/helper.js', () => { }); }); + + describe('Compose resolvers', () => { + const compositionErr = new Error('composition error'); + const successResolver = createResolver(() => null, () => null); + const failureResolver = createResolver(() => { throw compositionErr; }, () => null); + + it('composed resolvers are chained, and base resolver is called for each', () => { + + const b = { + resolve: () => {}, + error: d => compositionErr + }; + + stub(b, 'resolve', b.resolve); + + const base = new Composable(b.resolve, b.error); + const comp = base.compose({ + r1: () => true, + r2: () => true, + r3: () => true, + }); + + return Promise.all([ + + comp.r1().then(r => { + expect(b.resolve.calledThrice).to.be.true; + expect(r).to.be.true; + }), + + comp.r2().then(r => { + expect(b.resolve.calledThrice).to.be.true; + expect(r).to.be.true; + }), + + comp.r3().then(r => { + expect(r).to.be.true; + expect(b.resolve.calledThrice).to.be.true; + }) + + ]); + }); + + it('when base throws, child is not called ', () => { + + const b = { + resolve: null, + error: d => compositionErr + }; + + const r1 = { + resolve: () => true, + error: () => compositionErr + }; + + stub(b, 'error', b.error); + stub(r1, 'error', r1.error); + + const base = new Composable(b.resolve, b.error); + const comp = base.compose( { r1: r1 } ); + + comp.r1() + .catch( e => { + expect(b.error.calledOnce).to.be.true; + expect(r1.resolve.notCalled).to.be.true; + expect(r1.error.notCalled).to.be.true; + expect(e).to.equal(compositionErr); + }); + }); + + it('when child throws, parent error is called ', () => { + + const b = { + resolve: null, + error: d => null + }; + + const r1 = { + resolve: () => true, + error: () => compositionErr + }; + + stub(b, 'error', b.error); + stub(r1, 'error', r1.error); + + const base = new Composable(b.resolve, b.error); + const comp = base.compose( { r1: r1 } ); + + comp.r1() + .catch( e => { + expect(b.error.calledOnce).to.be.true; + expect(r1.error.calledOnce).to.be.true; + expect(e).to.equal(compositionErr); + }); + }); + + }); });