graphql-compose-modules
Advanced tools
Comparing version 1.5.0 to 1.6.0
@@ -14,2 +14,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.astMerge = void 0; | ||
function astMerge() { | ||
@@ -16,0 +17,0 @@ var asts = []; |
@@ -21,2 +21,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createFields = exports.astToSchema = void 0; | ||
var graphql_compose_1 = require("graphql-compose"); | ||
@@ -23,0 +24,0 @@ var dedent_1 = __importDefault(require("dedent")); |
"use strict"; | ||
var __spreadArrays = (this && this.__spreadArrays) || function () { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
var __spreadArray = (this && this.__spreadArray) || function (to, from) { | ||
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) | ||
to[j] = from[i]; | ||
return to; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.forEachKey = exports.visitNode = exports.astVisitor = exports.VISITOR_REMOVE_NODE = exports.VISITOR_SKIP_CHILDREN = void 0; | ||
/** | ||
@@ -68,3 +67,3 @@ * Do not traverse children | ||
name: name, | ||
path: __spreadArrays(info.path, [info.name]), | ||
path: __spreadArray(__spreadArray([], info.path), [info.name]), | ||
operation: info.operation, | ||
@@ -71,0 +70,0 @@ }); |
@@ -17,2 +17,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getAstForFile = exports.getAstForDir = exports.directoryToAst = exports.defaultOptions = void 0; | ||
var fs_1 = __importDefault(require("fs")); | ||
@@ -27,3 +28,4 @@ var path_1 = require("path"); | ||
// otherwise, resolve path relative to the equivalent of __dirname | ||
var schemaPath = (options === null || options === void 0 ? void 0 : options.relativePath) ? path_1.resolve(path_1.dirname(m.filename), options.relativePath) | ||
var schemaPath = (options === null || options === void 0 ? void 0 : options.relativePath) | ||
? path_1.resolve(path_1.dirname(m.filename), options.relativePath) | ||
: path_1.dirname(m.filename); | ||
@@ -30,0 +32,0 @@ // setup default options |
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.astMerge = exports.VISITOR_SKIP_CHILDREN = exports.VISITOR_REMOVE_NODE = exports.astVisitor = exports.astToSchema = exports.directoryToAst = exports.loadSchemaComposer = exports.buildSchema = void 0; | ||
var directoryToAst_1 = require("./directoryToAst"); | ||
@@ -20,12 +28,12 @@ var astToSchema_1 = require("./astToSchema"); | ||
var directoryToAst_2 = require("./directoryToAst"); | ||
exports.directoryToAst = directoryToAst_2.directoryToAst; | ||
Object.defineProperty(exports, "directoryToAst", { enumerable: true, get: function () { return directoryToAst_2.directoryToAst; } }); | ||
var astToSchema_2 = require("./astToSchema"); | ||
exports.astToSchema = astToSchema_2.astToSchema; | ||
__export(require("./testHelpers")); | ||
Object.defineProperty(exports, "astToSchema", { enumerable: true, get: function () { return astToSchema_2.astToSchema; } }); | ||
__exportStar(require("./testHelpers"), exports); | ||
var astVisitor_1 = require("./astVisitor"); | ||
exports.astVisitor = astVisitor_1.astVisitor; | ||
exports.VISITOR_REMOVE_NODE = astVisitor_1.VISITOR_REMOVE_NODE; | ||
exports.VISITOR_SKIP_CHILDREN = astVisitor_1.VISITOR_SKIP_CHILDREN; | ||
Object.defineProperty(exports, "astVisitor", { enumerable: true, get: function () { return astVisitor_1.astVisitor; } }); | ||
Object.defineProperty(exports, "VISITOR_REMOVE_NODE", { enumerable: true, get: function () { return astVisitor_1.VISITOR_REMOVE_NODE; } }); | ||
Object.defineProperty(exports, "VISITOR_SKIP_CHILDREN", { enumerable: true, get: function () { return astVisitor_1.VISITOR_SKIP_CHILDREN; } }); | ||
var astMerge_1 = require("./astMerge"); | ||
exports.astMerge = astMerge_1.astMerge; | ||
Object.defineProperty(exports, "astMerge", { enumerable: true, get: function () { return astMerge_1.astMerge; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -61,2 +61,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.testSDL = exports.testOperationErrors = exports.testOperationData = exports.testOperation = exports.testBuildSchema = void 0; | ||
var graphql_1 = require("graphql"); | ||
@@ -63,0 +64,0 @@ var graphql_compose_1 = require("graphql-compose"); |
{ | ||
"name": "graphql-compose-modules", | ||
"license": "MIT", | ||
"version": "1.5.0", | ||
"version": "1.6.0", | ||
"description": "A toolset for construction GraphQL Schema via file structure", | ||
@@ -17,26 +17,26 @@ "repository": "https://github.com/graphql-compose/graphql-compose-modules", | ||
"peerDependencies": { | ||
"graphql-compose": "^7.7.0" | ||
"graphql-compose": "^7.7.0 || ^8.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/dedent": "0.7.0", | ||
"@types/glob": "7.1.1", | ||
"@types/jest": "25.2.1", | ||
"@types/glob": "7.1.3", | ||
"@types/jest": "26.0.23", | ||
"@types/lodash.sortby": "^4.7.6", | ||
"@types/node": "13.11.0", | ||
"@typescript-eslint/eslint-plugin": "2.26.0", | ||
"@typescript-eslint/parser": "2.26.0", | ||
"apollo-server": "2.11.0", | ||
"eslint": "6.8.0", | ||
"eslint-config-prettier": "6.10.1", | ||
"eslint-plugin-prettier": "3.1.2", | ||
"graphql": "15.0.0", | ||
"graphql-compose": "7.14.1", | ||
"jest": "25.2.7", | ||
"@types/node": "15.0.1", | ||
"@typescript-eslint/eslint-plugin": "4.22.0", | ||
"@typescript-eslint/parser": "4.22.0", | ||
"apollo-server": "2.23.0", | ||
"eslint": "7.25.0", | ||
"eslint-config-prettier": "8.3.0", | ||
"eslint-plugin-prettier": "3.4.0", | ||
"graphql": "15.5.0", | ||
"graphql-compose": "8.0.1", | ||
"jest": "26.6.3", | ||
"lodash.sortby": "^4.7.0", | ||
"prettier": "2.0.2", | ||
"prettier": "2.2.1", | ||
"rimraf": "3.0.2", | ||
"semantic-release": "17.0.4", | ||
"ts-jest": "25.3.1", | ||
"ts-node": "8.8.2", | ||
"typescript": "3.8.3" | ||
"semantic-release": "17.4.2", | ||
"ts-jest": "26.5.5", | ||
"ts-node": "9.1.1", | ||
"typescript": "4.2.4" | ||
}, | ||
@@ -47,3 +47,3 @@ "scripts": { | ||
"build": "rimraf ./lib && tsc --build tsconfig.build.json", | ||
"lint": "eslint 'src/**/*.{js,ts}'", | ||
"lint": "eslint --ext .ts ./src", | ||
"test": "npm run lint && npm run tscheck && npm run coverage", | ||
@@ -50,0 +50,0 @@ "tscheck": "tsc --noEmit", |
235
README.md
@@ -11,1 +11,236 @@ # graphql-compose-modules | ||
[![Sponsors on Open Collective](https://opencollective.com/graphql-compose/sponsors/badge.svg)](#sponsors) | ||
This is a toolkit for creating big GraphQL schemas with code-first approach in JavaScript. | ||
## Quick demo | ||
You may find a simple GraphQL server example in the following folder: [examples/simple](./examples/simple). | ||
## GraphQL schema entrypoints from a file structure | ||
When you are using code-first approach in GraphQL Schema construction you may face problem when you cannot understand which entrypoints your schema has. And where exactly the code is placed which serve this or that entrypoint. | ||
![overview](./docs/diagrams/overview.drawio.svg) | ||
`graphql-compose-modules` uses a file-system based schema entrypoint definition (something like NextJS does with its pages concept for routing). You just create folder `schema/` and put inside it the following sub-folders (root directories): `query`, `mutation` and `subscription`. Inside these folders you may put `.js` or `.ts` files with FieldConfigs which describes entrypoints. Assume you create the following directory structure: | ||
```bash | ||
schema/ | ||
query/ | ||
articleById.ts | ||
articlesList.ts | ||
mutation/ | ||
createArticle.ts | ||
updateArticle.ts | ||
removeArticle.ts | ||
subscription/ | ||
onArticleChange.ts | ||
``` | ||
With this directory structure `graphql-compose-modules` will use file names as field names for your root types and you get the following GraphQL schema: | ||
```graphql | ||
type Query { | ||
articleById: ... | ||
articlesList: ... | ||
} | ||
type Mutation { | ||
createArticle: ... | ||
updateArticle: ... | ||
removeArticle: ... | ||
} | ||
type Subscription { | ||
onArticleChange: ... | ||
} | ||
``` | ||
If you want rename field `articlesList` to `articles` in your schema just rename `articlesList.ts` file. If you want to add a new field to Schema – just add a new file to `Query`, `Mutation`, `Subscription` folders. **This simple approach helps you understand entrypoints of your schema without launching the GraphQL server – what you see in folders that you get in GraphQL Schema**. | ||
## Describing Entrypoints in files | ||
Every Entrypoint (FieldConfig definition) is described in a separate file. This file contains information about input args, output type, resolve function and additional fields like description, deprecationReason, extensions. As an example let's create `schema/Query/sum.ts` and put inside the following content: | ||
```ts | ||
export default { | ||
type: 'Int!', | ||
args: { | ||
a: 'Int!', | ||
b: 'Int!', | ||
}, | ||
resolve: (source, args, context, info) => { | ||
return args.a + args.b; | ||
}, | ||
description: 'This method sums two numbers', | ||
deprecationReason: 'This method is deprecated and will be removed soon.', | ||
extensions: { | ||
someExtraParam: 'Can be used for AST transformers', | ||
}, | ||
}; | ||
``` | ||
If you are familiar with [graphql-js FieldConfig definition](https://graphql.org/graphql-js/type/#examples) then you may notice that `type` & `args` properties are defined in SDL format. This syntax sugar is provided by [graphql-compose](https://github.com/graphql-compose/graphql-compose#examples) package. | ||
## Entrypoints with namespaces for big schemas | ||
If your GraphQL Schema has a lot of entrypoints you may create sub-folders for grouping them under Namespaces: | ||
```bash | ||
schema/ | ||
query/ | ||
articles/ | ||
byId.ts | ||
list.ts | ||
... | ||
mutation/ | ||
articles/ | ||
create.ts | ||
update.ts | ||
remove.ts | ||
... | ||
``` | ||
With such structure you will get the following schema – namespace types `QueryArticles` & `MutationArticles` are created automatically: | ||
```graphql | ||
type Query { | ||
articles: QueryArticles | ||
} | ||
type Mutation { | ||
articles: MutationArticles | ||
} | ||
type QueryArticles { | ||
byId: ... | ||
list: ... | ||
} | ||
type MutationArticles { | ||
create: ... | ||
update: ... | ||
remove: ... | ||
} | ||
``` | ||
You may use namespaces (sub-folders) for `Query` & `Mutation` and all servers supports this feature. But for `Subscription` most current server implementations (eg. [apollo-server](https://www.apollographql.com/docs/apollo-server/data/subscriptions/)) does not support this yet. | ||
## GraphQLSchema construction | ||
In `schema` folder create a file `index.ts` with the following content which traverses `query`, `mutation`, `subscription` folders and creates a `GraphQLSchema` instance for you: | ||
```ts | ||
import { buildSchema } from 'graphql-compose-modules'; | ||
export const schema = buildSchema(module); | ||
``` | ||
After that you may create a GraphQL server: | ||
```ts | ||
import { ApolloServer } from 'apollo-server'; | ||
import { schema } from './schema'; | ||
const server = new ApolloServer({ schema }); | ||
server.listen().then(({ url }) => { | ||
console.log(`🚀 Server ready at ${url}`); | ||
}); | ||
``` | ||
## Advanced GraphQLSchema construction | ||
If you want transform AST of entrypoints (e.g. for adding authorization, logging, tracing) and for merging with another schemas distributed via npm packages – you may use the following advanced way for schema construction: | ||
```ts | ||
import { directoryToAst, astToSchema, astMerge } from 'graphql-compose-modules'; | ||
import { addQueryToMutations } from './transformers/addQueryToMutations'; | ||
import { remoteServiceAST } from '@internal/some-service'; | ||
// traverse `query`, `mutation`, `subscription` folders placed near this module | ||
let ast = directoryToAst(module); | ||
// apply transformer which uses astVisitor() method under the hood | ||
addQueryToMutations(ast); | ||
// merge with other ASTs distributed via npm packages | ||
ast = astMerge(ast, remoteServiceAST); | ||
// construct SchemaComposer | ||
const sc = astToSchema(ast); | ||
// construct GraphQLSchema instance and export it | ||
export const schema = sc.buildSchema(); | ||
``` | ||
## Writing own transformer for entrypoints | ||
For writing your own transformers you need to use `astVisitor()` method. For instance let's implement `addQueryToMutations` transformer which adds `query: Query` field to all your mutations: | ||
```ts | ||
import { astVisitor, VISITOR_SKIP_CHILDREN, AstRootNode } from 'graphql-compose-modules'; | ||
import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose'; | ||
export function addQueryToMutations( | ||
ast: AstRootNode, | ||
schemaComposer: SchemaComposer<any> | ||
): void { | ||
astVisitor(ast, { | ||
// skip `query` & `subscriptions` root types | ||
ROOT_TYPE: (node) => { | ||
if (node.name !== 'mutation') { | ||
return VISITOR_SKIP_CHILDREN; | ||
} | ||
return; | ||
}, | ||
// for every file in `mutation` folder try to add `query` field if it does not exists | ||
FILE: (node, nodeInfo) => { | ||
const fieldConfig = node.code.default || {}; | ||
const next = fieldConfig.resolve; | ||
if (!next) return; | ||
const outputType = fieldConfig.type; | ||
if (!outputType) return; | ||
const outputTC = schemaComposer.typeMapper.convertOutputTypeDefinition( | ||
outputType, | ||
nodeInfo.name, | ||
nodeInfo?.parent?.name | ||
); | ||
if (!(outputTC instanceof ObjectTypeComposer)) return; | ||
if (outputTC.hasField('query')) return; | ||
outputTC.setField('query', { | ||
description: 'Sub-query which have to be executed after mutation.', | ||
type: schemaComposer.Query, | ||
}); | ||
fieldConfig.resolve = async (s: any, args: any, context: any, i: any) => { | ||
const result = await next(s, args, context, i); | ||
return { | ||
query: {}, | ||
...result, | ||
}; | ||
}; | ||
}, | ||
}); | ||
} | ||
``` | ||
## API | ||
For now here is provided basic overview of the available API methods. Then detailed information will be provided later. | ||
### Main API method: | ||
- `buildSchema(module: NodeModule, opts: BuildOptions): GraphQLSchema` – use this method for creating graphql schema from directory | ||
### Advanced API methods: | ||
The following methods help to use schema composition, applying middlewares and schema transformation via visitor pattern: | ||
![overview](./docs/diagrams/ast-transformation.drawio.svg) | ||
- `directoryToAst(module: NodeModule, options: DirectoryToAstOptions): AstRootNode` – traverses directories and construct AST for your graphql entrypoints | ||
- `astToSchema(ast: AstRootNode, opts: AstToSchemaOptions): SchemaComposer` – converts AST to GraphQL Schema | ||
- `astMerge(...asts: Array<AstRootNode>): AstRootNode` – combines several ASTs to one AST (helps compose several graphql schemas which may be distributed via npm packages) | ||
- `astVisitor(ast: AstRootNode, visitor: AstVisitor): void` – modify AST via visitor pattern. This method is used for construction of your AST transformers. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
74320
824
246