From 2881de2bb9986454ad5d64d605fd7f3080de8709 Mon Sep 17 00:00:00 2001 From: Jason Morita Date: Fri, 7 Jul 2017 13:23:29 -0700 Subject: [PATCH 1/4] Add hash_ids flag to override default integer values --- README.md | 10 ++++- package.json | 1 + src/ExtractGQL.ts | 37 ++++++++++++++----- .../ApolloNetworkInterface.ts | 31 +++++++++++++++- typings.d.ts | 5 +++ 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 43550ff..2f4edac 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ npm install --save persistgraphql The build tool binary is called `persistgraphql`. Running it with no other arguments should give: ``` -Usage: persistgraphql input_file [output file] [--add_typename] +Usage: persistgraphql input_file [output file] [--add_typename] [--hash_ids] ``` It can be called on a file containing GraphQL query definitions with extension `.graphql`: @@ -49,6 +49,14 @@ persistgraphql index.ts output.json It can also take the `--add_typename` flag which will apply a query transformation to the query documents, adding the `__typename` field at every level of the query. You must pass this option if your client code uses this query transformation. +``` +persistgraphql src/ --hash_ids +``` + +## Using a hash of the query as an ID to allow scaling for multiple clients + +Use the optional `hash_ids` flag to substitute a `sha512` hash of the query as the map value rather then the default which is an incremental integer. This will avoid ID collisions for multiple clients using the same server. + ``` persistgraphql src/ --add_typename ``` diff --git a/package.json b/package.json index 858d6e4..5c7eda2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "apollo-client": "^1.1", "graphql": "^0.9.4", "graphql-tag": "^2.0.0", + "hasha": "^3.0.0", "lodash": "^4.17.4", "whatwg-fetch": "^2.0.3", "yargs": "^7.1.0" diff --git a/src/ExtractGQL.ts b/src/ExtractGQL.ts index 51b30f1..f28c77d 100644 --- a/src/ExtractGQL.ts +++ b/src/ExtractGQL.ts @@ -2,6 +2,7 @@ import fs = require('fs'); import path = require('path'); +import hasha = require('hasha'); import { parse, @@ -42,11 +43,12 @@ import { import _ = require('lodash'); export type ExtractGQLOptions = { + extension?: string, + hashIds?: boolean, + inJsCode?: boolean, inputFilePath: string, outputFilePath?: string, queryTransformers?: QueryTransformer[], - extension?: string, - inJsCode?: boolean, } export class ExtractGQL { @@ -63,6 +65,9 @@ export class ExtractGQL { // The file extension to load queries from public extension: string; + // Whether to use a hash of the query as an ID in the query map + public hashIds: boolean = false; + // Whether to look for standalone .graphql files or template literals in JavaScript code public inJsCode: boolean = false; @@ -106,17 +111,19 @@ export class ExtractGQL { } constructor({ + extension = 'graphql', + hashIds = false, + inJsCode = false, inputFilePath, outputFilePath = 'extracted_queries.json', queryTransformers = [], - extension = 'graphql', - inJsCode = false, }: ExtractGQLOptions) { + this.extension = extension; + this.hashIds = hashIds; + this.inJsCode = inJsCode; this.inputFilePath = inputFilePath; this.outputFilePath = outputFilePath; this.queryTransformers = queryTransformers; - this.extension = extension; - this.inJsCode = inJsCode; } // Add a query transformer to the end of the list of query transformers. @@ -153,7 +160,7 @@ export class ExtractGQL { const transformedQueryWithFragments = this.getQueryFragments(transformedDocument, transformedDefinition); transformedQueryWithFragments.definitions.unshift(transformedDefinition); const docQueryKey = this.getQueryDocumentKey(transformedQueryWithFragments); - result[docQueryKey] = this.getQueryId(); + result[docQueryKey] = this.hashIds ? hasha(docQueryKey) : this.getQueryId(); }); return result; } @@ -280,12 +287,12 @@ export class ExtractGQL { return carry; }; - + retDocument.definitions = document.definitions.reduce( reduceQueryDefinitions, ([] as FragmentDefinitionNode[]) ).sort(sortFragmentsByName); - + return retDocument; } @@ -333,6 +340,7 @@ export const main = (argv: YArgsv) => { // These are the unhypenated arguments that yargs does not process // further. const args: string[] = argv._ + let hashIds: boolean = false; let inputFilePath: string; let outputFilePath: string; const queryTransformers: QueryTransformer[] = []; @@ -353,7 +361,18 @@ export const main = (argv: YArgsv) => { queryTransformers.push(addTypenameTransformer); } + // Check if we are passed "--hash_ids", if we are, we have to + // use a hash of the query as an ID instead of integers. + // The hash_ids flag will use a sha512 hash of the query as the map value + //rather then the default which is an incremental integer + // This will avoid ID collisions for multiple clients using the same server. + if (argv['hash_ids']) { + console.log('Using hash of query as ID.'); + hashIds = true; + } + const options: ExtractGQLOptions = { + hashIds, inputFilePath, outputFilePath, queryTransformers, diff --git a/test/network_interface/ApolloNetworkInterface.ts b/test/network_interface/ApolloNetworkInterface.ts index 594649f..1cb02c1 100644 --- a/test/network_interface/ApolloNetworkInterface.ts +++ b/test/network_interface/ApolloNetworkInterface.ts @@ -1,3 +1,4 @@ +import hasha = require('hasha'); import * as chai from 'chai'; const { assert } = chai; @@ -90,6 +91,34 @@ describe('PersistedQueryNetworkInterface', () => { }); }); + describe('Using --hash_ids', () => { + const egql = new ExtractGQL({ hashIds: true, inputFilePath: 'nothing' }); + const queriesDocument = gql` + query getAuthor { + author { + firstName + lastName + } + } + query getHouse { + house { + address + } + } + `; + const queryMap = egql.createMapFromDocument(queriesDocument); + const keys = Object.keys(queryMap); + + it('should use a hash of the query as the value if --hash_ids is true', () => { + assert.equal(queryMap[keys[0]], hasha(keys[0])); + assert.equal(queryMap[keys[1]], hasha(keys[1])); + }); + + it('should not use an integer as the value if --hash_ids is true', () => { + assert.notEqual(queryMap[keys[0]], 1); + }); + }); + describe('sending query ids', () => { const egql = new ExtractGQL({ inputFilePath: 'nothing' }); const queriesDocument = gql` @@ -376,7 +405,7 @@ describe('addPersistedQueries', () => { const networkInterface = new GenericNetworkInterface(); addPersistedQueries(networkInterface, queryMap); const expectedId = queryMap[getQueryDocumentKey(request.query)]; - return networkInterface.query(request).then((persistedQuery: persistedQueryType) => { + return networkInterface.query(request).then((persistedQuery: any) => { const id = persistedQuery.id; const variables = persistedQuery.variables; const operationName = persistedQuery.operationName; diff --git a/typings.d.ts b/typings.d.ts index 2a4d25b..960b2ca 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -18,3 +18,8 @@ declare module 'deep-assign' { function deepAssign(...objects: any[]): any; export = deepAssign; } + +declare module 'hasha' { + function hasha(...objects: any[]): any; + export = hasha; +} From 6f010578f9a51ad27a500826756e94bb0da6a567 Mon Sep 17 00:00:00 2001 From: Jason Morita Date: Fri, 7 Jul 2017 16:12:14 -0700 Subject: [PATCH 2/4] Fix typo then vs than --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f4edac..077a92c 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ persistgraphql src/ --hash_ids ## Using a hash of the query as an ID to allow scaling for multiple clients -Use the optional `hash_ids` flag to substitute a `sha512` hash of the query as the map value rather then the default which is an incremental integer. This will avoid ID collisions for multiple clients using the same server. +Use the optional `hash_ids` flag to substitute a `sha512` hash of the query as the map value rather than the default which is an incremental integer. This will avoid ID collisions for multiple clients using the same server. ``` persistgraphql src/ --add_typename From 5e285a8f3c3381a1794b6f548f0cc2dafdeb9752 Mon Sep 17 00:00:00 2001 From: Jason Morita Date: Fri, 7 Jul 2017 22:57:04 -0700 Subject: [PATCH 3/4] Fix typo then vs than --- src/ExtractGQL.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExtractGQL.ts b/src/ExtractGQL.ts index f28c77d..52173db 100644 --- a/src/ExtractGQL.ts +++ b/src/ExtractGQL.ts @@ -364,7 +364,7 @@ export const main = (argv: YArgsv) => { // Check if we are passed "--hash_ids", if we are, we have to // use a hash of the query as an ID instead of integers. // The hash_ids flag will use a sha512 hash of the query as the map value - //rather then the default which is an incremental integer + //rather than the default which is an incremental integer // This will avoid ID collisions for multiple clients using the same server. if (argv['hash_ids']) { console.log('Using hash of query as ID.'); From 866e3817b4ee7ba359e0d3eaa00eda6eacc7cc58 Mon Sep 17 00:00:00 2001 From: Jason Morita Date: Fri, 7 Jul 2017 22:57:51 -0700 Subject: [PATCH 4/4] Add space in comment --- src/ExtractGQL.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExtractGQL.ts b/src/ExtractGQL.ts index 52173db..d6e287e 100644 --- a/src/ExtractGQL.ts +++ b/src/ExtractGQL.ts @@ -364,7 +364,7 @@ export const main = (argv: YArgsv) => { // Check if we are passed "--hash_ids", if we are, we have to // use a hash of the query as an ID instead of integers. // The hash_ids flag will use a sha512 hash of the query as the map value - //rather than the default which is an incremental integer + // rather than the default which is an incremental integer // This will avoid ID collisions for multiple clients using the same server. if (argv['hash_ids']) { console.log('Using hash of query as ID.');