diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..88edb62 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..11643ef --- /dev/null +++ b/.eslintrc @@ -0,0 +1,12 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..397cd02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp + +pids +logs +results +tmp + +# Coverage reports +coverage + +# API keys and secrets +.env + +# Dependency directory +node_modules +bower_components + +# Editors +.idea +*.iml + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built ts files +lib + +# ignore yarn.lock +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0bd484f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +semi: false +singleQuote: true +printWidth: 100 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..43004bb --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# graphql-fragments-manager + +`graphql-fragments-manager` helps with managing fragment dependencies when declaring graphql Queries, Mutations, Subscriptions + +## Example: + +```javascript +import { buildFragment, includeFragments } from 'graphql-fragments-manager' + +const UserPhotoVersionFragment = buildFragment( + gql` + fragment UserPhotoVersionFragment on UserPhotoVersion { + width + height + version + } + ` +) + +const UserPhotoFragment = buildFragment( + gql` + fragment UserPhotoFragment on UserPhoto { + url + versions { + ...UserPhotoVersionFragment + } + } + `, + [UserPhotoVersionFragment] +) + +const UserFragment = buildFragment( + gql` + fragment UserFragment on User { + id + photo { + ...UserPhotoFragment + } + } + `, + [UserPhotoFragment] +) + +const QUERY = gql` + query UserQuery($id: ID!) { + user(id: $id) { + ...UserFragment + } + } + + ${includeFragments(UserFragment)} +` + +/// INSTEAD OF: + +const QUERY = gql` + query UserQuery($id: ID!) { + user(id: $id) { + ...UserFragment + } + } + + ${UserFragment} + ${UserPhotoFragment} + ${UserPhotoVersionFragment} +` +``` + +`includeFragments` function will gather dependencies on all included fragments and will include all necessary fragments that are necessary for graphql. This helps avoid runtime errors when fragments are complex, with many nested fragments. diff --git a/package.json b/package.json new file mode 100644 index 0000000..e9d33b4 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "gql-fragments-manager", + "version": "0.0.1", + "description": "Library that helps to manage GraphQL fragment dependencies", + "main": "./lib/index.js", + "repository": "https://github.com/ajanauskas/gql-fragments-manager", + "author": "Andrius Janauskas ", + "license": "MIT", + "types": "./lib/index.d.ts", + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.23.0", + "@typescript-eslint/parser": "^4.23.0", + "eslint": "^7.26.0", + "eslint-plugin-prettier": "^3.4.0", + "graphql": "^15.5.0", + "prettier": "^2.3.0", + "typescript": "^4.2.4" + }, + "scripts": { + "lint": "eslint --ext '.js,.ts,.tsx' ." + } +} diff --git a/src/buildFragment.ts b/src/buildFragment.ts new file mode 100644 index 0000000..da4d2e1 --- /dev/null +++ b/src/buildFragment.ts @@ -0,0 +1,17 @@ +import { DocumentNode } from 'graphql' + +export type Fragment = { + fragment: DocumentNode + dependencies: ReadonlyArray +} + +export default ( + fragment: DocumentNode, + dependencies: ReadonlyArray | Fragment | undefined = undefined +): Fragment => ({ + fragment, + dependencies: + (dependencies && + ((Array.isArray(dependencies) ? dependencies : [dependencies]) as ReadonlyArray)) || + [], +}) diff --git a/src/includeFragments.ts b/src/includeFragments.ts new file mode 100644 index 0000000..49ebeb7 --- /dev/null +++ b/src/includeFragments.ts @@ -0,0 +1,31 @@ +import { DocumentNode } from 'graphql' +import { Fragment } from './buildFragment' + +function getAllDocumentNodes(fragment: Fragment): ReadonlyArray { + const allDocumentNodes: Array = [] + fragment.dependencies.forEach((dependency) => + allDocumentNodes.push(...getAllDocumentNodes(dependency)) + ) + allDocumentNodes.push(fragment.fragment) + return allDocumentNodes +} + +export default (fragments: ReadonlyArray | Fragment): string => { + const fragmentArray: ReadonlyArray = (Array.isArray(fragments) + ? fragments + : [fragments]) as ReadonlyArray + + const allDocumentNodes: Array = [] + + for (const fragment of fragmentArray) { + const documentNodes = getAllDocumentNodes(fragment) + + for (const documentNode of documentNodes) { + if (!allDocumentNodes.find(node => node === documentNode)) { + allDocumentNodes.push(documentNode) + } + } + } + + return allDocumentNodes.map((node) => node.loc?.source.body).join('\n') +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..8884800 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export type { Fragment } from './buildFragment' +export { default as buildFragment } from './buildFragment' +export { default as includeFragments } from './includeFragments' diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..738317d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "declaration": true, + "outDir": "./lib", + "strict": true + }, + "include": ["src"], + "exclude": ["node_modules", "**/__tests__/*"] +}