diff --git a/.gitignore b/.gitignore index 7eb80f0..c96954e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ node_modules # Transpiled code /es /lib +/__fixtures__ coverage .nyc_output diff --git a/package.json b/package.json index 235bc17..db7c79e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "graphql-compose": ">=2.13.1 || >=3.0.0 || >=4.0.0" }, "devDependencies": { + "aws-sdk": "^2.224.1", "babel-cli": "^6.26.0", "babel-eslint": "^8.2.3", "babel-jest": "^22.4.3", @@ -85,6 +86,7 @@ "docker:v2": "node ./scripts/docker/start 2 & wait", "docker:v5": "node ./scripts/docker/start 5 & wait", "link": "yarn build && yarn link graphql-compose && yarn link", - "unlink": "yarn unlink graphql-compose && yarn add graphql-compose" + "unlink": "yarn unlink graphql-compose && yarn add graphql-compose", + "my-demo": "./node_modules/.bin/babel-node ./__fixtures__/index.js" } } diff --git a/src/ElasticApiParser.js b/src/ElasticApiParser.js index ecce7a9..87a3923 100644 --- a/src/ElasticApiParser.js +++ b/src/ElasticApiParser.js @@ -75,7 +75,7 @@ export default class ElasticApiParser { parsedSource: ElasticParsedSourceT; constructor(opts: ElasticApiParserOptsT = {}) { - // avaliable varsions can be found in installed package `elasticsearch` + // avaliable versions can be found in installed package `elasticsearch` // in file /node_modules/elasticsearch/src/lib/apis/index.js this.apiVersion = opts.apiVersion || @@ -312,7 +312,6 @@ export default class ElasticApiParser { ...args, }); } - return client[elasticMethod]({ ...methodArgs, ...args }); }, }; diff --git a/src/composeWithElastic.js b/src/composeWithElastic.js index d32856e..4b0e9b0 100644 --- a/src/composeWithElastic.js +++ b/src/composeWithElastic.js @@ -4,6 +4,8 @@ import { TypeComposer } from 'graphql-compose'; import { convertToSourceTC, inputPropertiesToGraphQLTypes } from './mappingConverter'; import createSearchResolver from './resolvers/search'; import createSearchConnectionResolver from './resolvers/searchConnection'; +import createFindByIdResolver from './resolvers/findById'; +import createUpdateByIdResolver from './resolvers/updateById'; import type { ElasticMappingT } from './mappingConverter'; @@ -63,9 +65,13 @@ export function composeWithElastic(opts: composeWithElasticOptsT): TypeComposer const searchR = createSearchResolver(fieldMap, sourceTC, opts); const searchConnectionR = createSearchConnectionResolver(searchR, opts); + const findByIdR = createFindByIdResolver(fieldMap, sourceTC, opts); + const updateByIdR = createUpdateByIdResolver(fieldMap, sourceTC, opts); sourceTC.addResolver(searchR); sourceTC.addResolver(searchConnectionR); + sourceTC.addResolver(findByIdR); + sourceTC.addResolver(updateByIdR); return sourceTC; } diff --git a/src/resolvers/__tests__/findById-test.js b/src/resolvers/__tests__/findById-test.js new file mode 100644 index 0000000..769a1a0 --- /dev/null +++ b/src/resolvers/__tests__/findById-test.js @@ -0,0 +1,28 @@ +/* @flow */ + +import { Resolver } from 'graphql-compose'; +import createFindByIdResolver from '../findById'; +import elasticClient from '../../__mocks__/elasticClient'; +import { CvTC, CvFieldMap } from '../../__mocks__/cv'; + +const findByIdResolver = createFindByIdResolver(CvFieldMap, CvTC, { + elasticClient, + elasticIndex: 'cv', + elasticType: 'cv', +}); + +describe('findById', () => { + it('return instance of Resolver', () => { + expect(findByIdResolver).toBeInstanceOf(Resolver); + }); + + it('check args', () => { + expect(findByIdResolver.hasArg('id')).toBeTruthy(); + }); + + it('resolve', () => { + findByIdResolver.resolve({ args: { id: '4554' }, context: { elasticClient } }).then(res => { + console.log(res); // eslint-disable-line + }); + }); +}); diff --git a/src/resolvers/findById.js b/src/resolvers/findById.js new file mode 100644 index 0000000..58778d3 --- /dev/null +++ b/src/resolvers/findById.js @@ -0,0 +1,73 @@ +/* @flow */ + +import { Resolver, TypeComposer } from 'graphql-compose'; +import type { ResolveParams } from 'graphql-compose'; +import type { FieldsMapByElasticType } from '../mappingConverter'; +import ElasticApiParser from '../ElasticApiParser'; +import { getFindByIdOutputTC } from '../types/FindByIdOutput'; + +export type ElasticResolverOpts = { + prefix?: ?string, + elasticIndex: string, + elasticType: string, + elasticClient: Object, +}; + +export default function createFindByIdResolver( + fieldMap: FieldsMapByElasticType, + sourceTC: TypeComposer, + opts: ElasticResolverOpts +): Resolver { + if (!fieldMap || !fieldMap._all) { + throw new Error( + 'First arg for Resolver findById() should be fieldMap of FieldsMapByElasticType type.' + ); + } + + if (!sourceTC || sourceTC.constructor.name !== 'TypeComposer') { + throw new Error('Second arg for Resolver findById() should be instance of TypeComposer.'); + } + + const prefix = opts.prefix || 'Es'; + + const parser = new ElasticApiParser({ + elasticClient: opts.elasticClient, + prefix, + }); + + const findByIdFC = parser.generateFieldConfig('get', { + index: opts.elasticIndex, + type: opts.elasticType, + }); + + const argsConfigMap = { + id: 'String!', + }; + + const type = getFindByIdOutputTC({ prefix, fieldMap, sourceTC }); + + return new Resolver({ + type, + name: 'findById', + kind: 'query', + args: argsConfigMap, + resolve: async (rp: ResolveParams<*, *>) => { + const { source, args, context, info } = rp; + + if (!args.id) { + throw new Error(`Missed 'id' argument!`); + } + + const res = await findByIdFC.resolve(source, args, context, info); + const { _index, _type, _id, _version, _source } = res || {}; + + return { + _index, + _type, + _id, + _version, + ..._source, + }; + }, + }); +} diff --git a/src/resolvers/search.js b/src/resolvers/search.js index 3426981..2dc55d2 100644 --- a/src/resolvers/search.js +++ b/src/resolvers/search.js @@ -8,7 +8,7 @@ import ElasticApiParser from '../ElasticApiParser'; import { getSearchBodyITC, prepareBodyInResolve } from '../elasticDSL/SearchBody'; import { getSearchOutputTC } from '../types/SearchOutput'; -export type ElasticSearchResolverOpts = { +export type ElasticResolverOpts = { prefix?: ?string, elasticIndex: string, elasticType: string, @@ -18,7 +18,7 @@ export type ElasticSearchResolverOpts = { export default function createSearchResolver( fieldMap: FieldsMapByElasticType, sourceTC: TypeComposer, - opts: ElasticSearchResolverOpts + opts: ElasticResolverOpts ): Resolver { if (!fieldMap || !fieldMap._all) { throw new Error( diff --git a/src/resolvers/updateById.js b/src/resolvers/updateById.js new file mode 100644 index 0000000..cf9947b --- /dev/null +++ b/src/resolvers/updateById.js @@ -0,0 +1,93 @@ +/* @flow */ + +import { Resolver, TypeComposer, InputTypeComposer, type ResolveParams } from 'graphql-compose'; +import type { FieldsMapByElasticType } from '../mappingConverter'; +import ElasticApiParser from '../ElasticApiParser'; +import { getUpdateByIdOutputTC } from '../types/UpdateByIdOutput'; +import { getTypeName, getOrSetType, desc } from '../utils'; + +export type ElasticResolverOpts = { + prefix?: ?string, + elasticIndex: string, + elasticType: string, + elasticClient: Object, +}; + +export default function createUpdateByIdResolver( + fieldMap: FieldsMapByElasticType, + sourceTC: TypeComposer, + opts: ElasticResolverOpts +): Resolver { + if (!fieldMap || !fieldMap._all) { + throw new Error( + 'First arg for Resolver updateById() should be fieldMap of FieldsMapByElasticType type.' + ); + } + + if (!sourceTC || sourceTC.constructor.name !== 'TypeComposer') { + throw new Error('Second arg for Resolver updateById() should be instance of TypeComposer.'); + } + + const prefix = opts.prefix || 'Es'; + + const parser = new ElasticApiParser({ + elasticClient: opts.elasticClient, + prefix, + }); + + const updateByIdFC = parser.generateFieldConfig('update', { + index: opts.elasticIndex, + type: opts.elasticType, + _source: true, + }); + + const argsConfigMap = { + id: 'String!', + record: getRecordITC(fieldMap).getTypeAsRequired(), + }; + + const type = getUpdateByIdOutputTC({ prefix, fieldMap, sourceTC }); + + return new Resolver({ + type, + name: 'updateById', + kind: 'mutation', + args: argsConfigMap, + resolve: async (rp: ResolveParams<*, *>) => { + const { source, args, context, info } = rp; + + args.body = { + doc: { + ...args.record, + }, + }; + delete args.record; + + const res = await updateByIdFC.resolve(source, args, context, info); + + const { _index, _type, _id, _version, result, get } = res || {}; + const { _source } = get || {}; + + return { + _id, + _index, + _type, + _version, + result, + ..._source, + }; + }, + }); +} + +export function getRecordITC(fieldMap: FieldsMapByElasticType): InputTypeComposer { + const name = getTypeName('Record', {}); + const description = desc(`The record from Elastic Search`); + return getOrSetType(name, () => + InputTypeComposer.create({ + name, + description, + fields: { ...fieldMap._all }, + }) + ); +} diff --git a/src/types/FindByIdOutput.js b/src/types/FindByIdOutput.js new file mode 100644 index 0000000..5ca88ba --- /dev/null +++ b/src/types/FindByIdOutput.js @@ -0,0 +1,29 @@ +/* @flow */ + +import { TypeComposer } from 'graphql-compose'; +import { getTypeName, getOrSetType } from '../utils'; +import type { FieldsMapByElasticType } from '../mappingConverter'; + +export type FindByIdOptsT = { + prefix?: string, + postfix?: string, + fieldMap?: FieldsMapByElasticType, + sourceTC: TypeComposer, +}; + +export function getFindByIdOutputTC(opts: FindByIdOptsT): TypeComposer { + const name = getTypeName('FindByIdOutput', opts); + const { sourceTC } = opts || {}; + return getOrSetType(name, () => + TypeComposer.create({ + name, + fields: { + _id: 'String', + _index: 'String', + _type: 'String', + _version: 'Int', + ...sourceTC.getFields(), + }, + }) + ); +} diff --git a/src/types/UpdateByIdOutput.js b/src/types/UpdateByIdOutput.js new file mode 100644 index 0000000..788b1dd --- /dev/null +++ b/src/types/UpdateByIdOutput.js @@ -0,0 +1,30 @@ +/* @flow */ + +import { TypeComposer } from 'graphql-compose'; +import { getTypeName, getOrSetType } from '../utils'; +import type { FieldsMapByElasticType } from '../mappingConverter'; + +export type UpdateByIdOptsT = { + prefix?: string, + postfix?: string, + fieldMap?: FieldsMapByElasticType, + sourceTC: TypeComposer, +}; + +export function getUpdateByIdOutputTC(opts: UpdateByIdOptsT): TypeComposer { + const name = getTypeName('UpdateByIdOutput', opts); + const { sourceTC } = opts || {}; + return getOrSetType(name, () => + TypeComposer.create({ + name, + fields: { + _id: 'String', + _index: 'String', + _type: 'String', + _version: 'Int', + result: 'String', + ...sourceTC.getFields(), + }, + }) + ); +} diff --git a/yarn.lock b/yarn.lock index 6e10f05..7fac7c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -480,6 +480,21 @@ atob@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" +aws-sdk@^2.224.1: + version "2.224.1" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.224.1.tgz#82fe93e10b3e818f315c35ce8667cdc8db94a0b3" + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.8" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.17" + xmlbuilder "4.2.1" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -1134,6 +1149,10 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base64-js@^1.0.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.3.tgz#fb13668233d9614cf5fb4bce95a9ba4096cdf801" + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1294,7 +1313,15 @@ buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" -builtin-modules@^1.0.0: +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -2223,6 +2250,10 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + exec-sh@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10" @@ -3053,6 +3084,14 @@ iconv-lite@^0.4.17: version "0.4.18" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" +ieee754@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + +ieee754@^1.1.4: + version "1.1.11" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" + ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" @@ -3808,6 +3847,10 @@ jest@^22.4.3: import-local "^1.0.0" jest-cli "^22.4.3" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -4997,6 +5040,10 @@ pstree.remy@^1.1.0: dependencies: ps-tree "^1.1.0" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -5029,6 +5076,10 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + randomatic@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" @@ -5519,7 +5570,11 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.1.1" -sax@^1.2.4: +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + +sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -6302,6 +6357,13 @@ url-template@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + use@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" @@ -6329,14 +6391,14 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" +uuid@3.1.0, uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" -uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - v8flags@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" @@ -6491,6 +6553,19 @@ xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" +xml2js@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xmlbuilder@4.2.1, xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"