├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── package.json ├── sample ├── complex-types │ ├── README.md │ ├── index.ts │ ├── schema.ts │ ├── screenshot.png │ └── services.ts └── minimal │ ├── README.md │ ├── index.ts │ ├── schema.ts │ └── screenshot.png ├── src ├── build.ts ├── extensible.ts ├── index.ts ├── interface-builder.ts ├── interfaces-private.ts ├── interfaces.ts ├── inversify-list.ts ├── inversify-nonnull.ts ├── object-builder.ts ├── partial-map.ts ├── schema-builder.ts ├── shortcuts.ts ├── type-cache.ts ├── union-builder.ts └── utils.ts ├── test ├── inversify-extensible-graphql-spec.ts ├── inversify-graphql-spec.ts └── types.ts ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | Thumbs.db 3 | node_modules 4 | package-lock.json 5 | bin/**/* 6 | compile-cache 7 | src/compile-cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | - 8.8.1 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch minimal sample", 8 | "cwd": "${workspaceFolder}", 9 | "args": ["${workspaceFolder}/sample/minimal/index.ts"], 10 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 11 | "protocol": "inspector", 12 | "sourceMaps": true, 13 | "env": { 14 | "NODE_ENV": "development" 15 | }, 16 | "skipFiles": [ 17 | "/**" 18 | ] 19 | },{ 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Launch complex sample", 23 | "cwd": "${workspaceFolder}", 24 | "args": ["${workspaceFolder}/sample/complex-types/index.ts"], 25 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 26 | "protocol": "inspector", 27 | "sourceMaps": true, 28 | "env": { 29 | "NODE_ENV": "development" 30 | }, 31 | "skipFiles": [ 32 | "/**" 33 | ] 34 | }, 35 | { 36 | "name": "Debug Mocha Tests", 37 | "type": "node", 38 | "request": "attach", 39 | "port": 9229, 40 | "protocol": "inspector", 41 | "timeout": 30000, 42 | "stopOnEntry": false, 43 | "skipFiles": [ 44 | "${workspaceRoot}/node_modules/**/*.js", 45 | "/**/*.js" 46 | ], 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaExplorer.files": "test/**/*-spec.ts", 3 | "mochaExplorer.require": [ 4 | "ts-node/register" 5 | ], 6 | "mochaExplorer.debuggerConfig": "Debug Mocha Tests", 7 | "files.exclude": { 8 | "coverage": true, 9 | "bin": true 10 | } 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 inversify-graphql 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inversify-graphql 2 | 3 | ![build status](https://travis-ci.org/oguimbal/inversify-graphql.svg?branch=master) 4 | [![npm version](https://badge.fury.io/js/inversify-graphql.svg)](https://badge.fury.io/js/inversify-graphql) 5 | [![Dependencies](https://david-dm.org/inversify/InversifyJS.svg)](https://david-dm.org/oguimbal/inversify-graphql#info=dependencies) 6 | [![img](https://david-dm.org/inversify/InversifyJS/dev-status.svg)](https://david-dm.org/oguimbal/inversify-graphql/#info=devDependencies) 7 | [![img](https://david-dm.org/inversify/InversifyJS/peer-status.svg)](https://david-dm.org/oguimbal/inversify-graphql/#info=peerDependenciess) 8 | [![Known Vulnerabilities](https://snyk.io/test/github/oguimbal/inversify-graphql/badge.svg)](https://snyk.io/test/github/oguimbal/inversify-graphql) 9 | 10 | Build dependency-inverted GraphQL schemas with [InversifyJS](https://github.com/inversify/InversifyJS) 11 | 12 | # Quickstart 13 | 14 | See: 15 | - the [sample app](sample/minimal/README.md) for a minimal sample 16 | - the [complex types app](sample/complex-types/README.md) to dive inito more complex inversified types 17 | 18 | 19 | # Usage 20 | 21 | Install the package 22 | ``` 23 | npm i inversify reflect-metadata graphql inversify-graphql --save 24 | ``` 25 | 26 | Example using [express](https://www.npmjs.com/package/express) and [apollo-server](https://www.npmjs.com/package/apollo-server) 27 | 28 | ```typescript 29 | import { inversifySchema } from 'inversify-graphql'; 30 | /* ... initialize express & inversify container */ 31 | const srv = new agql.ApolloServer({ 32 | // build inversified schema 33 | context: /* whateverContext */, 34 | schema: inversifySchema(myContainer, { 35 | query: MyRootQuery, 36 | }), 37 | }); 38 | srv.applyMiddleware({ app, path: '/graphql'}); 39 | 40 | 41 | ``` 42 | 43 | ```typescript 44 | // === MyRootQuery definition == 45 | export class MyRootQuery extends InversifyObjectTypeBuilder { 46 | 47 | // Injected dependency, usable in our resolve() function 48 | @inject(MyDependency) dependency: MyDependency; 49 | 50 | config(): InversifyObjectConfig { 51 | return { 52 | name: 'MyRoot', 53 | // nb: "fields" supports 'partial roots', enabling you to describe one object in multiple separate builders 54 | // fields: [PartialRoot1, PartialRoot2], 55 | fields: { 56 | // compatible with classic GraphQL objects/types 57 | classicField: { 58 | type: GraphQLString, 59 | resolve: () => 'classic' 60 | }, 61 | // use your "type builders" to refernece inversified field types 62 | inversifiedField: { 63 | type: MyType, 64 | resolve: () => this.dependency.getWhateverEntity() 65 | }, 66 | // use InversifiedList to build a GraphQLList of an inversified type. 67 | inversifiedListField: { 68 | type: InversifyList(MyType), 69 | resolve: () => this.dependency.getWhateverList() 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | ``` 77 | ```typescript 78 | // === MyType definition == 79 | export class MyType extends InversifyObjectTypeBuilder { 80 | 81 | // Injected dependency, usable in our resolve() function 82 | @inject(MyDependency) dependency: MyDependency; 83 | 84 | config(): InversifyObjectConfig { 85 | return { 86 | // ... sub fields, using source, context AND inversified dependencies (injectable in this class) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | # Simple inline type definition 93 | 94 | You can define sub-types "inline" (cleaner syntax) 95 | 96 | ```typescript 97 | export class MyType extends InversifyObjectTypeBuilder { 98 | 99 | // Injected dependency, usable in our resolve() function 100 | @inject(MyDependency) dependency: MyDependency; 101 | 102 | config(): InversifyObjectConfig { 103 | return { 104 | myField: { 105 | // resolver 106 | resolve: () => 42, 107 | // inline type definition 108 | type: { 109 | name: 'MyInlineType', 110 | fields: { 111 | subField: { 112 | type: GraphQLString, 113 | resolve: x => x + 'is the answer', // will output "42 is the answer" 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | ``` 122 | 123 | 124 | # Handy shortcuts 125 | 126 | If like me, you're annoyed with those very long names like `new GraphQLList(new GraphQLNonNull(GraphQLString)))`, you can use the `inversify-graphql/shortcuts` helpers to turn them into `GList(NN(GString))`. 127 | 128 | **nb:** shortcuts are compatible with inversified types ;) 129 | 130 | # Modular schema definiton 131 | 132 | Some type definitions can tend to be bloated as your app grows. 133 | In these cases, you might want to split the definition of types in several files or parts of your application. 134 | 135 | This is the purpose of "extensible schema", which builds on top of inversify-graphql to enable you to define a more modular schema. 136 | 137 | ```typescript 138 | 139 | const adminSchema = extensibleSchema('RootName', container); 140 | 141 | // those two partial roots will be merged in the schema root 142 | adminSchema.query.merge(PartialRoot1); 143 | adminSchema.query.merge(PartialRoot2); 144 | adminSchema.mutation.merge(PartialMutationRoot2); 145 | 146 | // extend a type 147 | // the type 'MyTypeToExtend' will augmented with all the fields defined in MyPartialMap 148 | // nb: this will work even if 'MyTypeToExtend' has not been defined yet 149 | adminSchema.get('MyTypeToExtend').merge(MyPartialMap); 150 | 151 | // you can concatenate two schemas: 152 | // this will augment the root of "adminSchema" with everything defined in "userSchma" 153 | adminSchema.concat(userSchma); 154 | ``` 155 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inversify-graphql", 3 | "version": "1.2.3", 4 | "publisher": "Olivier Guimbal", 5 | "description": "Builds dependency-inverted GraphQL schemas with InversifyJS", 6 | "main": "index.js", 7 | "types": "index.d.ts", 8 | "scripts": { 9 | "typecheck": "tsc --project tsconfig.json --noEmit", 10 | "build": "rimraf bin && tsc -p tsconfig.build.json", 11 | "test": "mocha -r ts-node/register test/**/*-spec.ts", 12 | "sample": "node -r ts-node/register sample/minimal/index.ts", 13 | "sample-complex": "node -r ts-node/register sample/complex-types/index.ts", 14 | "release": "npm run build && cp README.md bin/README.md && cp package.json bin/package.json && npm publish bin" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+ssh://git@github.com/oguimbal/inversify-graphql.git" 19 | }, 20 | "keywords": [ 21 | "inversify", 22 | "inversifyjs", 23 | "graphql", 24 | "graphql-server", 25 | "apollo-server" 26 | ], 27 | "author": "Olivier Guimbal", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/oguimbal/inversify-graphql/issues" 31 | }, 32 | "homepage": "https://github.com/oguimbal/inversify-graphql#readme", 33 | "devDependencies": { 34 | "@types/chai": "^4.1.7", 35 | "@types/express": "^4.16.0", 36 | "@types/graphql": "^14.0.3", 37 | "@types/mocha": "^5.2.5", 38 | "@types/node": "^10.12.12", 39 | "apollo-server-express": "^2.5.0", 40 | "chai": "^4.2.0", 41 | "express": "^4.16.4", 42 | "graphql": "^14.0.2", 43 | "inversify": "5.0.1", 44 | "mocha": "5.2.0", 45 | "node-ts": "^2.1.2", 46 | "reflect-metadata": "^0.1.12", 47 | "rimraf": "^3.0.2", 48 | "ts-node": "^7.0.1", 49 | "tslint": "5.9.1", 50 | "typescript": "^3.7.5" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/complex-types/README.md: -------------------------------------------------------------------------------- 1 | This sample demonstrates how to use both `inversify-graphql/shortcuts` module and more complex types. 2 | 3 | **NOTA BENE:** This barely demonstrates how to actually use injection (only on `BikeType` class). It is rather a set of examples of how to mix more complex inversified types. 4 | 5 | To run it: 6 | 7 | ``` 8 | git clone git@github.com:oguimbal/inversify-graphql.git 9 | cd inversify-graphql 10 | npm i 11 | npm run sample-complex 12 | ``` 13 | 14 | (or just press F5 when in vscode with the right configuration) 15 | 16 | You should then be able to navigate GraphiQL via http://localhost:3000/ 17 | 18 | Try typing the below request: 19 | 20 | ```graphql 21 | { 22 | inline{a b} 23 | partialRoots { 24 | len upper original 25 | } 26 | vehicules { 27 | ...on IVehicule {name wheels} 28 | ...on Car {honk} 29 | ...on Bike{pedals} 30 | } 31 | } 32 | ``` 33 | 34 | 35 | ![alt text](screenshot.png) -------------------------------------------------------------------------------- /sample/complex-types/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import express from 'express'; 3 | import * as agql from 'apollo-server-express'; 4 | import {Container} from 'inversify'; 5 | import {inversifySchema} from '../../src'; 6 | import { SchemaBuilder } from './schema'; 7 | import { MyDependency, MyContext } from './services'; 8 | 9 | const app = express() 10 | const port = 3000 11 | 12 | // bind some fake dependencies 13 | const container = new Container(); 14 | container.bind(MyDependency).toConstantValue(new MyDependency()); 15 | 16 | // create the graphql server 17 | const diagSrv = new agql.ApolloServer({ 18 | context: new MyContext('context data passed to graphql'), 19 | schema: inversifySchema(container, SchemaBuilder), 20 | }); 21 | 22 | diagSrv.applyMiddleware({ 23 | app, 24 | path: '/', 25 | cors: true, 26 | }); 27 | 28 | app.listen(port, () => console.log(`Example app listening on http://localhost:${port} ! 29 | 30 | Try the request: 31 | 32 | 33 | { 34 | classicField, 35 | inversifiedField { 36 | len, 37 | repeated 38 | } 39 | inversifiedListField { 40 | len, 41 | repeated 42 | } 43 | }`)) 44 | -------------------------------------------------------------------------------- /sample/complex-types/schema.ts: -------------------------------------------------------------------------------- 1 | import { InversifySchemaBuilder, InversifySchemaConfig, InversifyObjectTypeBuilder, InversifyObjectConfig, InversifyPartialMap, InversifyFieldConfigMap } from '../../src'; 2 | import { inject, injectable } from 'inversify'; 3 | import { GString, GList, GObjectInv, GInt, GInterfaceInv, GPartialMap, GObject, GUnionInv, GBool } from '../../src/shortcuts'; 4 | import { MyDependency, MyContext } from './services'; 5 | 6 | /** 7 | * ENTRY POINT 8 | */ 9 | export class SchemaBuilder extends InversifySchemaBuilder { 10 | schema(): InversifySchemaConfig { 11 | return { 12 | query: MyRootQuery, 13 | // you could use other graphql schema config fields: 14 | // mutation, subscription, ... 15 | } 16 | } 17 | } 18 | 19 | /** 20 | * Root query definition 21 | */ 22 | class MyRootQuery extends InversifyObjectTypeBuilder { 23 | 24 | // Injected dependency, usable in our resolve() function 25 | @inject(MyDependency) dependency: MyDependency; 26 | 27 | config(): InversifyObjectConfig { 28 | return { 29 | name: 'MyRoot', 30 | fields: { 31 | // === inline type === 32 | inline: { 33 | type: GObjectInv({ 34 | name: 'InlineType', 35 | fields: { 36 | a: { type: GInt }, 37 | b: { type: GInt }, 38 | } 39 | }), 40 | resolve: () => ({ a: 42, b: 51 }), 41 | }, 42 | 43 | // === Partial roots === 44 | partialRoots: { 45 | // this will make a type 'PartialRoots' with 3 properties: 'length', 'upper', 'original' 46 | // (see implementations below) 47 | type: GObjectInv({ 48 | name: 'PartialRoots', 49 | fields: [ 50 | LengthPartialMap, 51 | UpperPartialMap, 52 | // you can define partial maps inline: 53 | GPartialMap({ original: { type: GString, resolve: x => x } }) 54 | ], 55 | }), 56 | resolve: x => 'some string', 57 | }, 58 | 59 | // === Union + interfaces === 60 | vehicules: { 61 | type: GList(TwoVehiculesUnion), 62 | resolve: () => [ 63 | { type: 'car', name: 'Honda' }, 64 | { type: 'car', name: 'Tesla' }, 65 | { type: 'bike', name: 'Kawazaki' }, 66 | ] 67 | } 68 | 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | const IVehiculeType = GInterfaceInv({ 76 | name: 'IVehicule', 77 | fields: { 78 | name: { type: GString }, 79 | wheels: { type: GInt }, 80 | }, 81 | resolveType: t => { 82 | switch (t.type) { 83 | case 'car': 84 | return CarType; 85 | case 'bike': 86 | return BikeType; 87 | } 88 | } 89 | }); 90 | 91 | const CarType = GObjectInv({ 92 | name: 'Car', 93 | interfaces: () => [IVehiculeType], 94 | fields: () => ({ 95 | name: { type: GString }, 96 | honk: { type: GString, resolve: () => 'Honk !' }, 97 | wheels: { type: GInt, resolve: () => 4 } 98 | }) 99 | }); 100 | 101 | 102 | /** Lets say this one uses a depdendency, for fun */ 103 | class BikeType extends InversifyObjectTypeBuilder { 104 | 105 | @inject(MyDependency) private dep: MyDependency; 106 | 107 | config() { 108 | return { 109 | name: 'Bike', 110 | interfaces: [IVehiculeType], 111 | fields: () => ({ 112 | name: { type: GString }, 113 | wheels: { type: GInt, resolve: () => 2 }, 114 | pedals: { type: GBool, resolve: () => this.dep.doesBikesHavePedals() }, 115 | }) 116 | } 117 | } 118 | } 119 | 120 | const TwoVehiculesUnion = GUnionInv({ 121 | name: 'TerrestrialVehicle', 122 | types: [BikeType, CarType], 123 | resolveType: t => { 124 | switch (t.type) { 125 | case 'car': 126 | return CarType; 127 | case 'bike': 128 | return BikeType; 129 | } 130 | } 131 | }) 132 | 133 | class LengthPartialMap extends InversifyPartialMap { 134 | map(): InversifyFieldConfigMap { 135 | return { 136 | len: { type: GInt, resolve: x => x.length }, 137 | }; 138 | } 139 | } 140 | 141 | class UpperPartialMap extends InversifyPartialMap { 142 | map(): InversifyFieldConfigMap { 143 | return { 144 | upper: { type: GString, resolve: x => x.toUpperCase() }, 145 | }; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /sample/complex-types/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oguimbal/inversify-graphql/b5e89fc0dba48fc303ab011183988a6b97c00f52/sample/complex-types/screenshot.png -------------------------------------------------------------------------------- /sample/complex-types/services.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from 'inversify'; 2 | 3 | /** Fake context class */ 4 | export class MyContext { 5 | constructor(public contextData: string) { 6 | 7 | } 8 | } 9 | 10 | @injectable() 11 | export class MyDependency { 12 | doesBikesHavePedals() { 13 | return true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/minimal/README.md: -------------------------------------------------------------------------------- 1 | This is a quite simplistic sample that demonstrates how to use inversify-graphql 2 | 3 | To run it: 4 | 5 | ``` 6 | git clone git@github.com:oguimbal/inversify-graphql.git 7 | cd inversify-graphql 8 | npm i 9 | npm run sample 10 | ``` 11 | 12 | (or just press F5 when in vscode) 13 | 14 | You should then be able to navigate GraphiQL via http://localhost:3000/ 15 | 16 | Try typing the below request: 17 | 18 | ```graphql 19 | { 20 | classicField, 21 | inversifiedField { 22 | len, 23 | repeated 24 | } 25 | inversifiedListField { 26 | len, 27 | repeated 28 | } 29 | } 30 | ``` 31 | 32 | 33 | ![alt text](screenshot.png) -------------------------------------------------------------------------------- /sample/minimal/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import express from 'express'; 3 | import * as agql from 'apollo-server-express'; 4 | import {Container} from 'inversify'; 5 | import {inversifySchema} from '../../src'; 6 | import { SchemaBuilder, MyContext, MyDependency } from './schema'; 7 | 8 | const app = express() 9 | const port = 3000 10 | 11 | // bind some fake dependencies 12 | const container = new Container(); 13 | container.bind(MyDependency).toConstantValue(new MyDependency(['Hello', 'World'])); 14 | 15 | // create the graphql server 16 | const diagSrv = new agql.ApolloServer({ 17 | context: new MyContext('context data passed to graphql'), 18 | schema: inversifySchema(container, SchemaBuilder), 19 | }); 20 | 21 | diagSrv.applyMiddleware({ 22 | app, 23 | path: '/', 24 | cors: true, 25 | }); 26 | 27 | app.listen(port, () => console.log(`Example app listening on http://localhost:${port} ! 28 | 29 | Try the request: 30 | 31 | 32 | { 33 | classicField, 34 | inversifiedField { 35 | len, 36 | repeated 37 | } 38 | inversifiedListField { 39 | len, 40 | repeated 41 | } 42 | }`)) 43 | -------------------------------------------------------------------------------- /sample/minimal/schema.ts: -------------------------------------------------------------------------------- 1 | import { InversifySchemaBuilder, InversifyList, InversifySchemaConfig, InversifyObjectTypeBuilder, InversifyObjectConfig } from '../../src'; 2 | import { inject, injectable } from 'inversify'; 3 | import { GraphQLInt, GraphQLString } from 'graphql'; 4 | import { GString, GList } from '../../src/shortcuts'; 5 | 6 | /** Fake context class */ 7 | export class MyContext { 8 | constructor(public contextData: string) { 9 | 10 | } 11 | } 12 | 13 | @injectable() 14 | export class MyDependency { 15 | constructor(public dependencyData: string[]) { 16 | } 17 | 18 | getFirst() { 19 | return this.dependencyData[0]; 20 | } 21 | 22 | getAll() { 23 | return this.dependencyData; 24 | } 25 | } 26 | 27 | export class SchemaBuilder extends InversifySchemaBuilder { 28 | 29 | 30 | schema(): InversifySchemaConfig { 31 | return { 32 | query: MyRootQuery, 33 | // you could use other graphql schema config fields: 34 | // mutation, subscription, ... 35 | } 36 | } 37 | 38 | } 39 | 40 | /** 41 | * Root query definition 42 | */ 43 | export class MyRootQuery extends InversifyObjectTypeBuilder { 44 | 45 | // Injected dependency, usable in our resolve() function 46 | @inject(MyDependency) dependency: MyDependency; 47 | 48 | config(): InversifyObjectConfig { 49 | return { 50 | name: 'MyRoot', 51 | // nb: "fields" supports 'partial roots', enabling you to describe one object in multiple separate builders 52 | // fields: [PartialRoot1, PartialRoot2], 53 | fields: { 54 | // compatible with classic GraphQL objects/types 55 | classicField: { 56 | type: GraphQLString, 57 | resolve: (_, args, ctx) => ctx.contextData, 58 | }, 59 | // use your "type builders" to refernece inversified field types 60 | inversifiedField: { 61 | type: MyType, 62 | resolve: () => this.dependency.getFirst() 63 | }, 64 | // use InversifiedList to build a GraphQLList of an inversified type. 65 | inversifiedListField: { 66 | type: GList(MyType), 67 | resolve: () => this.dependency.getAll() 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * A type definition that has 'string' as context 76 | */ 77 | export class MyType extends InversifyObjectTypeBuilder { 78 | 79 | // Injected dependency, usable in our resolve() function 80 | @inject(MyDependency) dependency: MyDependency; 81 | 82 | config(): InversifyObjectConfig { 83 | return { 84 | name: 'MyType', 85 | fields: { 86 | len: { type: GraphQLInt, resolve: x => x.length }, 87 | repeated: { type: GraphQLString, resolve: x => x.repeat(3) }, 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /sample/minimal/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oguimbal/inversify-graphql/b5e89fc0dba48fc303ab011183988a6b97c00f52/sample/minimal/screenshot.png -------------------------------------------------------------------------------- /src/build.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { InversifySchemaBuilder } from './schema-builder'; 4 | import { InversifySchemaConfig } from './interfaces'; 5 | import { TypeCache } from './type-cache'; 6 | import { ITypeCache } from './interfaces-private'; 7 | 8 | export function inversifySchema(container: inv.Container, config: inv.interfaces.Newable | InversifySchemaConfig): gql.GraphQLSchema { 9 | 10 | // create child container 11 | const thisContainer = new inv.Container(); 12 | thisContainer.parent = container; 13 | const types = new TypeCache(thisContainer); 14 | thisContainer.bind(ITypeCache).toConstantValue(types); 15 | 16 | let builtConfig: InversifySchemaConfig; 17 | if (typeof config === 'function') { 18 | builtConfig = types.get(config).schema(); 19 | } else 20 | builtConfig = config; 21 | 22 | // resolve builders 23 | if (typeof builtConfig.query === 'function') { 24 | const builder = thisContainer.resolve(builtConfig.query); 25 | builtConfig.query = builder.build(); 26 | } 27 | if (typeof builtConfig.mutation === 'function') { 28 | const builder = thisContainer.resolve(builtConfig.mutation); 29 | builtConfig.mutation = builder.build(); 30 | } 31 | 32 | if (typeof builtConfig.subscription === 'function') { 33 | const builder = thisContainer.resolve(builtConfig.subscription); 34 | builtConfig.subscription = builder.build(); 35 | } 36 | 37 | // build schema 38 | return new gql.GraphQLSchema({ 39 | // typescript only detects correct types doing this: 40 | ...builtConfig, 41 | mutation: builtConfig.mutation, 42 | query: builtConfig.query, 43 | subscription: builtConfig.subscription, 44 | }); 45 | } -------------------------------------------------------------------------------- /src/extensible.ts: -------------------------------------------------------------------------------- 1 | import { interfaces, Container, inject, typeConstraint, injectable } from 'inversify'; 2 | import { InversifyPartialMap } from './partial-map'; 3 | import { GraphQLSchema, GraphQLNamedType } from 'graphql'; 4 | import { inversifySchema } from './build'; 5 | import { IInversifyExtensibleNode, InversifyObjectTypeBuilder, ExtensibleSchemaSymbol, IExtSchema, InversifyObjectTypeBuilderBase } from './object-builder'; 6 | import { ITypeCache } from './interfaces-private'; 7 | import { InversifyObjectConfig, InversifyFieldList, InversifySchemaConfig, IInversifyExtensibleSchema } from './interfaces'; 8 | import { InversifySchemaBuilder } from './schema-builder'; 9 | import { named } from './utils'; 10 | 11 | @injectable() 12 | export class InversifyExtensibleNode implements IInversifyExtensibleNode { 13 | 14 | private readonly extensions: interfaces.Newable>[] = []; 15 | typeName: string; 16 | useParentExtensions = false; 17 | for(name: string) { 18 | this.typeName = name; 19 | return this; 20 | } 21 | 22 | 23 | /** Merge the given field list definitions in the current node */ 24 | merge(...fields: interfaces.Newable>[]): this { 25 | this.extensions.push(...fields); 26 | return this; 27 | } 28 | 29 | 30 | buildType(): interfaces.Newable> { 31 | if (!this.extensions.length) 32 | return null; 33 | // create a temp class 34 | const that = this; 35 | class Temp extends InversifyObjectTypeBuilder { 36 | constructor() { 37 | super(); 38 | super.extensions = that.useParentExtensions ? 'noDirect' : 'none'; 39 | } 40 | 41 | config(): InversifyObjectConfig { 42 | const fieldsMap: InversifyFieldList = {}; 43 | 44 | // augment fields with extensions 45 | for (const ext of that.extensions) { 46 | let thisMap = this.builders.get(ext).map(); 47 | if (typeof thisMap === 'function') 48 | thisMap = thisMap(); 49 | for (const fname of Object.keys(thisMap)) { 50 | fieldsMap[fname] = thisMap[fname]; 51 | } 52 | } 53 | 54 | // return the type 55 | return { 56 | name: that.typeName, 57 | fields: fieldsMap, 58 | }; 59 | } 60 | } 61 | 62 | return named(Temp, this.typeName + '_Extensible'); 63 | } 64 | } 65 | 66 | 67 | 68 | export class InversifyExtensibleSchema implements IExtSchema, IInversifyExtensibleSchema { 69 | readonly query: InversifyExtensibleNode; 70 | readonly mutation: InversifyExtensibleNode; 71 | readonly subscription: InversifyExtensibleNode; 72 | readonly nodes = new Map>(); 73 | container: Container; 74 | private parents: this[] = []; 75 | private orphanTypes: GraphQLNamedType[] = []; 76 | 77 | constructor(name: string, container: Container) { 78 | const c = this.container = new Container(); 79 | c.parent = container; 80 | c.bind(Container).toConstantValue(c); 81 | c.bind(ExtensibleSchemaSymbol).toConstantValue(this); 82 | 83 | this.query = this.get(name + 'Queries'); 84 | this.mutation = this.get(name + 'Mutations'); 85 | this.subscription = this.get(name + 'Subscriptions'); 86 | for (const q of [this.query, this.mutation, this.subscription]) { 87 | q.useParentExtensions = true; 88 | } 89 | } 90 | 91 | private create(typeName: any) { 92 | return this.container.resolve>(InversifyExtensibleNode) 93 | .for(typeof typeName === 'string' ? typeName : null); 94 | } 95 | 96 | get(typeToExtend: string | interfaces.Newable>): InversifyExtensibleNode { 97 | let node = this.nodes.get(typeToExtend); 98 | if (!node) { 99 | this.nodes.set(typeToExtend, node = this.create(typeToExtend)); 100 | } 101 | return node; 102 | } 103 | 104 | addTypes(...additionalTypes: GraphQLNamedType[]) { 105 | this.orphanTypes.push(...additionalTypes); 106 | return this; 107 | } 108 | 109 | getNoCreate(extendedType: string, ctor: any, which: 'all' | 'noDirect' | 'none'): InversifyExtensibleNode[] { 110 | if (which === 'none') 111 | return []; 112 | const ret: InversifyExtensibleNode[] = []; 113 | // push extensions of parents first 114 | for (const p of this.parents) { 115 | ret.push(...p.getNoCreate(extendedType, ctor, 'all')); 116 | } 117 | // get parent extensions for roots 118 | for (const q of ['query', 'mutation', 'subscription']) { 119 | if (this[q].typeName === extendedType) { 120 | for (const p of this.parents) 121 | ret.push(p[q]); 122 | } 123 | } 124 | // finally, override all previous extensions by extensions in this schema 125 | if (which !== 'noDirect') { 126 | ret.push(this.nodes.get(extendedType)); 127 | ret.push(this.nodes.get(ctor)); 128 | } 129 | return ret.filter(x => !!x); 130 | } 131 | 132 | concat(...otherSchema: this[]): this { 133 | this.parents.push(...otherSchema); 134 | return this; 135 | } 136 | 137 | build(): GraphQLSchema { 138 | 139 | const that = this; 140 | class Temp extends InversifySchemaBuilder { 141 | 142 | @inject(ITypeCache) protected builders: ITypeCache; 143 | 144 | schema(): InversifySchemaConfig { 145 | return { 146 | query: that.query.buildType(), 147 | mutation: that.mutation.buildType(), 148 | subscription: that.subscription.buildType(), 149 | types: that.orphanTypes, 150 | } 151 | } 152 | } 153 | return inversifySchema(this.container, Temp); 154 | } 155 | } 156 | 157 | export function extensibleSchema(name: string, container: Container): IInversifyExtensibleSchema { 158 | return new InversifyExtensibleSchema(name, container); 159 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {inversifySchema} from './build'; 2 | export * from './interfaces'; 3 | export {InversifyObjectTypeBuilder} from './object-builder'; 4 | export {InversifyInterfaceTypeBuilder} from './interface-builder'; 5 | export {InversifyPartialMap} from './partial-map'; 6 | export {InversifySchemaBuilder} from './schema-builder'; 7 | export {InversifyList} from './inversify-list'; 8 | export {InversifyNonNull} from './inversify-nonnull'; 9 | export {extensibleSchema} from './extensible'; 10 | export {InversifyUnionTypeBuilder} from './union-builder'; -------------------------------------------------------------------------------- /src/interface-builder.ts: -------------------------------------------------------------------------------- 1 | import * as gql from 'graphql'; 2 | import { InversifyInterfaceConfig } from './interfaces'; 3 | import { InversifyObjectTypeBuilderBase } from './object-builder'; 4 | 5 | 6 | export abstract class InversifyInterfaceTypeBuilder 7 | extends InversifyObjectTypeBuilderBase> { 8 | 9 | protected built: gql.GraphQLInterfaceType; 10 | 11 | build() { 12 | if (this.built) 13 | return this.built; 14 | 15 | // build fields & co 16 | const cfg = super.doBuildConfig() as gql.GraphQLInterfaceTypeConfig; 17 | 18 | // return real object type 19 | this.built = new gql.GraphQLInterfaceType({ 20 | ...cfg, 21 | resolveType: async (value, ctx, info, resolveInfo) => { 22 | const ret = await cfg.resolveType(value, ctx, info, resolveInfo); 23 | if (!ret) 24 | return null; 25 | if (typeof ret === 'string') { 26 | return ret; 27 | } 28 | return this.builders.buildType(ret); 29 | } 30 | }); 31 | return this.built; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/interfaces-private.ts: -------------------------------------------------------------------------------- 1 | import { injectable, interfaces } from 'inversify'; 2 | import { InversifyType } from './interfaces'; 3 | import { GraphQLOutputType } from 'graphql'; 4 | 5 | export abstract class ITypeCache { 6 | abstract buildType(builder: InversifyType): GraphQLOutputType; 7 | abstract get(ctor: interfaces.Newable): T; 8 | } -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import Maybe from 'graphql/tsutils/Maybe'; 4 | import { InversifyObjectTypeBuilder, InversifyObjectTypeBuilderBase } from './object-builder'; 5 | import { InversifyPartialMap } from './partial-map'; 6 | import { InversifyUnionTypeBuilder } from './union-builder'; 7 | import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue'; 8 | import { InversifyInterfaceTypeBuilder } from './interface-builder'; 9 | 10 | type Omit = Pick> 11 | 12 | export interface InversifySchemaConfig 13 | extends Omit { 14 | query?: gql.GraphQLObjectType | Maybe>>; 15 | mutation?: gql.GraphQLObjectType | Maybe>>; 16 | subscription?: gql.GraphQLObjectType | Maybe>>; 17 | } 18 | /** 19 | * Inversify equivalent of GraphQLFieldConfigMap 20 | */ 21 | export interface InversifyFieldConfigMap { 22 | [key: string]: InversifyFieldConfig; 23 | } 24 | 25 | export type InversifyInlineType = InversifyObjectConfig; 26 | 27 | export type InversifyBuilder = InversifyObjectTypeBuilder 28 | | InversifyUnionTypeBuilder 29 | | InversifyInterfaceTypeBuilder; 30 | 31 | /** Types allowed as inversified types */ 32 | export type InversifyType = | gql.GraphQLOutputType 33 | | InversifyInlineType 34 | | inv.interfaces.Newable> 35 | ; 36 | 37 | export interface InversifyFieldConfig 38 | extends Omit, 'type'> { 39 | type: InversifyType; 40 | } 41 | 42 | export type InversifyFieldList = gql.Thunk> | inv.interfaces.Newable>[]; 43 | export type InversifyUnionTypeList = gql.Thunk[]>; 44 | 45 | export interface InversifyObjectConfig 46 | extends Omit, 'fields' | 'interfaces'> { 47 | /** 48 | * Fields can be either a map, or an array of partial map builder 49 | */ 50 | interfaces?: InversifyUnionTypeList; 51 | fields: InversifyFieldList; 52 | } 53 | 54 | export interface InversifyUnionConfig 55 | extends Omit, 'types' | 'resolveType'> { 56 | 57 | types: InversifyUnionTypeList; 58 | resolveType?: Maybe>; 59 | } 60 | 61 | export interface InversifyInterfaceConfig 62 | extends Omit, 'fields' | 'resolveType'> { 63 | 64 | fields: InversifyFieldList; 65 | resolveType?: Maybe>; 66 | } 67 | 68 | export type InversifyTypeResolver = ( 69 | value: TSource, 70 | context: TContext, 71 | info: gql.GraphQLResolveInfo 72 | ) => PromiseOrValue>>; 73 | 74 | 75 | export interface IInversifyExtensibleSchema { 76 | 77 | /** The child container of this schema */ 78 | readonly container: inv.Container; 79 | /** Enxtensible root query */ 80 | readonly query: IInversifyExtensibleNode; 81 | /** Enxtensible root mutation */ 82 | readonly mutation: IInversifyExtensibleNode; 83 | /** Enxtensible root subscription */ 84 | readonly subscription: IInversifyExtensibleNode; 85 | /** Get a type to extend by name */ 86 | get(typeToExtend: string | inv.interfaces.Newable>): IInversifyExtensibleNode; 87 | /** Concatenate extensions */ 88 | concat(...otherSchema: this[]): this; 89 | /** Add other orphan types */ 90 | addTypes(...additionalTypes: gql.GraphQLNamedType[]): this; 91 | 92 | /** Build the generated schema */ 93 | build(): gql.GraphQLSchema; 94 | } 95 | 96 | export interface IInversifyExtensibleNode { 97 | /** Merge the given field list definitions in the current node */ 98 | merge(...fields: inv.interfaces.Newable>[]): this 99 | } -------------------------------------------------------------------------------- /src/inversify-list.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { InversifyObjectTypeBuilder } from './object-builder'; 4 | import { InversifyObjectConfig } from '.'; 5 | import { named } from './utils'; 6 | import { InversifyBuilder } from './interfaces'; 7 | 8 | /** 9 | * Creates a GraphQL list of an inversify type 10 | * @param ctor The type builder to make a list of 11 | */ 12 | export function InversifyList(ctor: inv.interfaces.Newable>) 13 | : inv.interfaces.Newable> { 14 | 15 | class ThisList extends InversifyObjectTypeBuilder { 16 | 17 | private builtList: gql.GraphQLList; 18 | 19 | config(): InversifyObjectConfig { 20 | throw new Error('Invalid operation'); 21 | } 22 | 23 | build(): any { 24 | if (this.builtList) 25 | return this.builtList; 26 | const type = this.builders.get(ctor).build(); 27 | return this.builtList = new gql.GraphQLList(type); 28 | } 29 | } 30 | 31 | return named(ThisList, `List(${ctor.name})`); 32 | } -------------------------------------------------------------------------------- /src/inversify-nonnull.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { InversifyObjectTypeBuilder } from './object-builder'; 4 | import { InversifyObjectConfig } from '.'; 5 | import { named } from './utils'; 6 | import { InversifyBuilder } from './interfaces'; 7 | 8 | /** 9 | * Creates a GraphQLNonNull of an inversify type 10 | * @param ctor The type builder to make a list of 11 | */ 12 | export function InversifyNonNull(ctor: inv.interfaces.Newable>) 13 | : inv.interfaces.Newable> { 14 | 15 | class ThisNotNull extends InversifyObjectTypeBuilder { 16 | 17 | private builtNn: gql.GraphQLNonNull; 18 | 19 | config(): InversifyObjectConfig { 20 | throw new Error('Invalid operation'); 21 | } 22 | 23 | build(): any { 24 | if (this.builtNn) 25 | return this.builtNn; 26 | const type = this.builders.get(ctor).build(); 27 | return this.builtNn = new gql.GraphQLNonNull(type); 28 | } 29 | } 30 | 31 | return named(ThisNotNull, `NotNull(${ctor.name})`); 32 | } -------------------------------------------------------------------------------- /src/object-builder.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { InversifyObjectConfig, InversifyFieldConfigMap, InversifyInterfaceConfig } from './interfaces'; 4 | import { ITypeCache } from './interfaces-private'; 5 | 6 | export interface IInversifyExtensibleNode { 7 | buildType(): inv.interfaces.Newable>; 8 | } 9 | 10 | export const ExtensibleSchemaSymbol = Symbol(); 11 | 12 | export interface IExtSchema { 13 | getNoCreate(extendedType: string, ctor: any, which: 'all' | 'noDirect' | 'none'): IInversifyExtensibleNode[]; 14 | } 15 | 16 | 17 | @inv.injectable() 18 | export abstract class InversifyObjectTypeBuilderBase | InversifyInterfaceConfig) & Object> { 21 | 22 | protected building?: boolean; 23 | protected extensions: 'all' | 'noDirect' | 'none' = 'all'; 24 | 25 | @inv.inject(ITypeCache) protected builders: ITypeCache; 26 | @inv.inject(ExtensibleSchemaSymbol) @inv.optional() private extensible: IExtSchema; 27 | 28 | abstract config(): TConfig; 29 | 30 | protected doBuildConfig() { 31 | if (this.building) 32 | throw new Error(`The type ${this.constructor.name} is involved in a circlar referenciation loop. 33 | If this is intended, please use the thunk version of 'fields' - i.e. "fields: () => ({ /* field definition */ })`) 34 | this.building = true; 35 | 36 | 37 | // resolve fields, and 38 | // deep copy item (this.config() might return a constant => must not be modified) 39 | const cfg = {...this.config() }; 40 | if (typeof cfg.fields === 'function') { 41 | const cpy = cfg.fields; 42 | cfg.fields = () => ({ 43 | ...cpy() 44 | }); 45 | } else if (cfg.fields instanceof Array) { 46 | cfg.fields = [...cfg.fields]; 47 | } else { 48 | cfg.fields = { 49 | ...cfg.fields, 50 | }; 51 | } 52 | 53 | // resolve types 54 | // deep copy item (this.config() might return a constant => must not be modified) 55 | let interfaces: gql.Thunk; 56 | if ('interfaces' in cfg) { 57 | // typescript wont do the cast automatically :( 58 | const cfgo = cfg as InversifyObjectConfig; 59 | if (typeof cfgo.interfaces === 'function') { 60 | const cpy = cfgo.interfaces; 61 | cfgo.interfaces = () => [ ...cpy() ]; 62 | } else if (cfgo.interfaces instanceof Array) { 63 | cfgo.interfaces = [...cfgo.interfaces]; 64 | } 65 | 66 | // map types 67 | if (typeof cfgo.interfaces === 'function') { 68 | const cpy = cfgo.interfaces; 69 | interfaces = () => cpy().map(x => this.builders.buildType(x)); 70 | } else { 71 | interfaces = cfgo.interfaces.map(x => this.builders.buildType(x)); 72 | } 73 | } 74 | 75 | // load extensions 76 | if (this.extensible) { 77 | const extList = this.extensible.getNoCreate(cfg.name, Object.getPrototypeOf(this).constructor, this.extensions); 78 | for (const extended of extList) { 79 | const built = extended.buildType(); 80 | if (built) { 81 | const instanciated = this.builders.get(built); 82 | const extCfg = instanciated.config(); 83 | if (typeof cfg.fields === 'function') { 84 | const cpy = cfg.fields; 85 | cfg.fields = () => ({ 86 | ...cpy(), 87 | ...extCfg.fields, 88 | }) 89 | } else { 90 | cfg.fields = { 91 | ...cfg.fields, 92 | ...extCfg.fields, 93 | } 94 | // for (const fname of Object.keys(extCfg.fields)) { 95 | // if (fname in cfg.fields) 96 | // throw new Error('Cannot merge GraphQL extensions in ' + cfg.name + ' because an extension also declares a field named ' + fname); 97 | // cfg.fields[fname] = extCfg.fields[fname]; 98 | // } 99 | } 100 | } 101 | } 102 | } 103 | 104 | const buildField = (ifcm: InversifyFieldConfigMap) => { 105 | // build real type map 106 | const builtMap: gql.GraphQLFieldConfigMap = {}; 107 | Object.keys(ifcm) 108 | .forEach(fieldName => { 109 | const field = ifcm[fieldName]; 110 | if (!field) 111 | return; // ignore undefined fields 112 | const type = this.builders.buildType(field.type); 113 | builtMap[fieldName] = { 114 | ...field, 115 | type: type, 116 | }; 117 | }); 118 | return builtMap; 119 | } 120 | 121 | // retreive fields 122 | let builtFields: gql.Thunk>; 123 | if (cfg.fields instanceof Array) { 124 | builtFields = {}; 125 | cfg.fields.forEach(b => { 126 | let m = this.builders.get(b).map(); 127 | if (typeof m === 'function') 128 | m = m(); 129 | const builtMap = buildField(m); 130 | Object.keys(builtMap) 131 | .forEach(mk => { 132 | if (builtFields[mk]) 133 | throw new Error('Dupplicate field in partial graphql map: ' + mk); 134 | builtFields[mk] = builtMap[mk]; 135 | }); 136 | }); 137 | } else if (typeof cfg.fields === 'function') { 138 | const fieldsFn = cfg.fields; 139 | builtFields = () => buildField(fieldsFn()); 140 | } else 141 | builtFields = buildField(cfg.fields); 142 | 143 | 144 | delete this.building; 145 | return { 146 | ...cfg, 147 | interfaces, 148 | fields: builtFields, 149 | }; 150 | } 151 | } 152 | 153 | export abstract class InversifyObjectTypeBuilder 154 | extends InversifyObjectTypeBuilderBase> { 155 | 156 | protected built: gql.GraphQLObjectType; 157 | 158 | build() { 159 | if (this.built) 160 | return this.built; 161 | 162 | const cfg = super.doBuildConfig() as any; 163 | // return real object type 164 | this.built = new gql.GraphQLObjectType(cfg); 165 | return this.built; 166 | } 167 | } -------------------------------------------------------------------------------- /src/partial-map.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { InversifyFieldConfigMap } from './interfaces'; 4 | 5 | @inv.injectable() 6 | export abstract class InversifyPartialMap { 7 | abstract map(): gql.Thunk>; 8 | } -------------------------------------------------------------------------------- /src/schema-builder.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import { InversifySchemaConfig } from './interfaces'; 3 | 4 | @inv.injectable() 5 | export abstract class InversifySchemaBuilder { 6 | 7 | abstract schema(): InversifySchemaConfig; 8 | } 9 | -------------------------------------------------------------------------------- /src/shortcuts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ======= PURPOSE ========= 3 | * This file contains shortcuts to have a more readable GraphQL typing 4 | */ 5 | import { GraphQLNullableType, GraphQLNonNull, GraphQLType, GraphQLList, GraphQLInputObjectTypeConfig, GraphQLInputObjectType, GraphQLObjectTypeConfig, GraphQLObjectType, GraphQLUnionTypeConfig, GraphQLUnionType, Thunk, GraphQLEnumTypeConfig, GraphQLEnumType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean, GraphQLInterfaceTypeConfig, GraphQLInterfaceType, GraphQLScalarType } from 'graphql'; 6 | import { InversifyObjectConfig, InversifyObjectTypeBuilder, InversifyUnionConfig, InversifyUnionTypeBuilder, InversifyFieldConfigMap, InversifyPartialMap, InversifyInterfaceConfig, InversifyBuilder, InversifyList } from '.'; 7 | import { interfaces, injectable } from 'inversify'; 8 | import { named } from './utils'; 9 | import { InversifyInterfaceTypeBuilder } from './interface-builder'; 10 | import { InversifyObjectTypeBuilderBase } from './object-builder'; 11 | import { InversifyNonNull } from './inversify-nonnull'; 12 | 13 | type Ctor = interfaces.Newable; 14 | type InvBuilder = Ctor; 15 | 16 | function isPlainGql(t: any): t is GraphQLType { 17 | let i = 0; 18 | while (t && t !== Object && t !== Function && t !== Number && t !== Date) { 19 | i++; 20 | if (i > 100) { 21 | throw new Error('Unexpected prototype chain error'); 22 | } 23 | if (t === InversifyObjectTypeBuilderBase) { 24 | return false; 25 | } 26 | if (t === InversifyUnionTypeBuilder) { 27 | return false; 28 | } 29 | t = Object.getPrototypeOf(t); 30 | } 31 | return true; 32 | } 33 | 34 | export function NN(t: InvBuilder): InvBuilder; 35 | export function NN(t: T): GraphQLNonNull; 36 | export function NN(t: T | InvBuilder) { 37 | return isPlainGql(t) 38 | ? new GraphQLNonNull(t) 39 | : InversifyNonNull(t); 40 | } 41 | 42 | 43 | /** GraphQL list */ 44 | export function GList(t: InvBuilder): InvBuilder; 45 | export function GList(t: T): GraphQLList; 46 | export function GList(t: GraphQLType | Ctor) { 47 | const ttt = InversifyObjectTypeBuilder; 48 | const tt = InversifyObjectTypeBuilderBase; 49 | return isPlainGql(t) 50 | ? new GraphQLList(t) 51 | : InversifyList(t); 52 | } 53 | 54 | /** GraphQL input type */ 55 | export function GInput(cfg: GraphQLInputObjectTypeConfig) { 56 | return new GraphQLInputObjectType(cfg); 57 | } 58 | 59 | /** GraphQL object */ 60 | export function GObject(cfg: GraphQLObjectTypeConfig) { 61 | return new GraphQLObjectType(cfg); 62 | } 63 | 64 | 65 | /** Inversify object shortcut */ 66 | export function GObjectInv(cfg: InversifyObjectConfig): interfaces.Newable> { 67 | @injectable() 68 | class QuickInversified extends InversifyObjectTypeBuilder { 69 | 70 | config(): InversifyObjectConfig { 71 | return cfg; 72 | } 73 | } 74 | return named(QuickInversified, cfg.name); 75 | } 76 | 77 | export function GInterface(cfg: GraphQLInterfaceTypeConfig) { 78 | return new GraphQLInterfaceType(cfg); 79 | } 80 | 81 | /** Inversify interface */ 82 | export function GInterfaceInv(cfg: InversifyInterfaceConfig): interfaces.Newable> { 83 | @injectable() 84 | class QuickInversified extends InversifyInterfaceTypeBuilder { 85 | config(): InversifyInterfaceConfig { 86 | return cfg; 87 | } 88 | } 89 | return named(QuickInversified, cfg.name); 90 | } 91 | 92 | function trimResolvers(f) { 93 | if (typeof f === 'function') 94 | return () => trimResolvers(f()); 95 | const ret = Object.entries(f) 96 | .map(([k, v]) => { 97 | const nv = {...(v as any)}; 98 | delete nv.resolve; 99 | return [k, nv] as [any, any]; 100 | }) 101 | return Object.fromEntries(ret); 102 | } 103 | 104 | /** 105 | * Create both an input & output types, with the same shape. 106 | * 107 | * Usage: 108 | * 109 | * const [MyOutput, MyInput] = GObjectIO({ ...config... }) 110 | * 111 | */ 112 | export function GObjectIO(cfg: GraphQLObjectTypeConfig & GraphQLInputObjectTypeConfig) 113 | : [GraphQLObjectType, GraphQLInputObjectType] { 114 | return [GObject(cfg), GInput({ 115 | ...cfg, 116 | name: cfg.name + 'Input', 117 | fields: trimResolvers(cfg.fields), 118 | })]; 119 | } 120 | 121 | /** GraphQL union */ 122 | export function GUnion(cfg: GraphQLUnionTypeConfig) { 123 | return new GraphQLUnionType(cfg); 124 | } 125 | 126 | /** Inversify union */ 127 | export function GUnionInv(cfg: InversifyUnionConfig): interfaces.Newable> { 128 | @injectable() 129 | class QuickUnionInversified extends InversifyUnionTypeBuilder { 130 | 131 | config(): InversifyUnionConfig { 132 | return cfg; 133 | } 134 | } 135 | return named(QuickUnionInversified, cfg.name); 136 | } 137 | 138 | /** Inversify partial map shortcut */ 139 | export function GPartialMap(cfg: Thunk>): interfaces.Newable> { 140 | @injectable() 141 | class PartialMapCtor extends InversifyPartialMap { 142 | map(): Thunk> { 143 | return cfg; 144 | } 145 | } 146 | return PartialMapCtor; 147 | } 148 | 149 | /** GraphQL enum */ 150 | export function GEnum(cfg: GraphQLEnumTypeConfig) { 151 | return new GraphQLEnumType(cfg); 152 | } 153 | 154 | 155 | /** GraphQL string */ 156 | export const GString = GraphQLString; 157 | /** GraphQL int */ 158 | export const GInt = GraphQLInt; 159 | /** GraphQL float */ 160 | export const GFloat = GraphQLFloat; 161 | /** GraphQL bool */ 162 | export const GBool = GraphQLBoolean; 163 | // /** GraphQL json */ 164 | // export const GJson = GraphQLJSON; 165 | // /** GraphQL date */ 166 | // export const GDate = GraphQLDateTime; -------------------------------------------------------------------------------- /src/type-cache.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import { InversifyType, InversifyObjectConfig } from './interfaces'; 3 | import { GraphQLOutputType } from 'graphql'; 4 | import { InversifyObjectTypeBuilder } from './object-builder'; 5 | import { ITypeCache } from './interfaces-private'; 6 | 7 | /** 8 | * Holds singletons of all builders 9 | */ 10 | export class TypeCache extends ITypeCache { 11 | 12 | constructor(private container: inv.Container) { 13 | super(); 14 | } 15 | 16 | 17 | buildType(builder: InversifyType): GraphQLOutputType { 18 | if (typeof builder === 'function') { 19 | return this.get(builder).build(); 20 | } else if ('inspect' in builder) { 21 | return builder; 22 | } 23 | 24 | const fieldCfg = builder; 25 | class InlineType extends InversifyObjectTypeBuilder { 26 | config(): InversifyObjectConfig { 27 | return fieldCfg; 28 | } 29 | } 30 | return this.get(InlineType).build(); 31 | } 32 | 33 | get(ctor: inv.interfaces.Newable): T { 34 | if (!this.container.isBound(ctor)) 35 | this.container.bind(ctor).toSelf().inSingletonScope(); 36 | return this.container.get(ctor); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/union-builder.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import { ITypeCache } from './interfaces-private'; 4 | import { InversifyUnionConfig } from './interfaces'; 5 | 6 | @inv.injectable() 7 | export abstract class InversifyUnionTypeBuilder { 8 | 9 | protected built: gql.GraphQLUnionType; 10 | protected building?: boolean; 11 | protected extensions: 'all' | 'noDirect' | 'none' = 'all'; 12 | 13 | @inv.inject(ITypeCache) protected builders: ITypeCache; 14 | 15 | abstract config() : InversifyUnionConfig; 16 | 17 | build(): gql.GraphQLUnionType { 18 | 19 | if (this.built) 20 | return this.built; 21 | if (this.building) 22 | throw new Error(`The type ${this.constructor.name} is involved in a circlar referenciation loop. 23 | If this is intended, please use the thunk version of 'fields' - i.e. "fields: () => ({ /* field definition */ })`) 24 | this.building = true; 25 | 26 | // resolve types 27 | // deep copy item (this.config() might return a constant => must not be modified) 28 | const cfg = {...this.config() }; 29 | if (typeof cfg.types === 'function') { 30 | const cpy = cfg.types; 31 | cfg.types = () => [ ...cpy() ]; 32 | } else if (cfg.types instanceof Array) { 33 | cfg.types = [...cfg.types]; 34 | } 35 | 36 | // map types 37 | let types: gql.Thunk; 38 | if (typeof cfg.types === 'function') { 39 | const cpy = cfg.types; 40 | types = () => cpy().map(x => this.builders.buildType(x)); 41 | } else { 42 | types = cfg.types.map(x => this.builders.buildType(x)); 43 | } 44 | 45 | // return real object type 46 | this.built = new gql.GraphQLUnionType({ 47 | ...cfg, 48 | types: types, 49 | resolveType: async (value, ctx, info) => { 50 | const ret = await cfg.resolveType(value, ctx, info); 51 | if (!ret) 52 | return null; 53 | return this.builders.buildType(ret); 54 | } 55 | }); 56 | delete this.building; 57 | return this.built; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { interfaces } from 'inversify'; 2 | 3 | export function named>(Ctor: T, name: string): T { 4 | Object.defineProperty(Ctor.prototype, 'toString', { value: () => name}) 5 | Object.defineProperty(Ctor, 'name', { value: name }); 6 | Object.defineProperty(Ctor, 'toString', { value: () => name + 'ctor' }); 7 | return Ctor; 8 | } 9 | -------------------------------------------------------------------------------- /test/inversify-extensible-graphql-spec.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'mocha'; 3 | import { expect } from 'chai'; 4 | import * as inv from 'inversify'; 5 | import * as gql from 'graphql'; 6 | import { inversifySchema, extensibleSchema, InversifyPartialMap, IInversifyExtensibleSchema, IInversifyExtensibleNode } from '../src'; 7 | import { SchemaBuilder, schemaDefinition, Dependency, PartialRoot1, PartialRoot2, RootQuery, Type2, Type1 } from './types'; 8 | import { InversifyObjectTypeBuilderBase } from '../src/object-builder'; 9 | 10 | 11 | describe('graphql-inversify-extensible', () => { 12 | 13 | let container: inv.Container; 14 | let builder: IInversifyExtensibleSchema; 15 | beforeEach(() => { 16 | container = new inv.Container(); 17 | container.bind(Dependency).toSelf().inSingletonScope(); 18 | builder = extensibleSchema('XX', container); 19 | }) 20 | 21 | 22 | it('builds schema from object', () => { 23 | builder.query.merge(PartialRoot1, PartialRoot2); 24 | const schema = builder.build(); 25 | const rootQuery = schema.getQueryType(); 26 | const fields = rootQuery.getFields(); 27 | const keys = Object.keys(fields); 28 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 29 | }) 30 | 31 | it('concatenates schemas', () => { 32 | builder.query.merge(PartialRoot1); 33 | const builder2 = extensibleSchema('YY', container); 34 | builder.concat(builder2); 35 | builder2.query.merge(PartialRoot2); 36 | 37 | const schema = builder.build(); 38 | const schema2 = builder2.build(); 39 | 40 | const rootQuery = schema.getQueryType(); 41 | const fields = rootQuery.getFields(); 42 | const keys = Object.keys(fields); 43 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 44 | 45 | 46 | const rootQuery2 = schema2.getQueryType(); 47 | const fields2 = rootQuery2.getFields(); 48 | const keys2 = Object.keys(fields2); 49 | expect(keys2.length).to.equal(2, 'Expecting 3 fields on root'); 50 | 51 | 52 | const type2_1 = fields.partial2type2.type; 53 | const type2_2 = fields2.partial2type2.type; 54 | expect(type2_1 === type2_2).to.equal(false, 'Should have rebuilt this type'); 55 | }) 56 | 57 | it('builds schema from builder', () => { 58 | builder.query.merge(PartialRoot1, PartialRoot2); 59 | const schema = builder.build(); 60 | 61 | const rootQuery = schema.getQueryType(); 62 | 63 | // check root type 64 | let fields = rootQuery.getFields(); 65 | const keys = Object.keys(fields); 66 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 67 | expect(keys).to.deep.equal(['partial1type1', 'deep', 'partial2type2', 'partial2String']) 68 | expect(fields.partial1type1.type).to.be.instanceof(gql.GraphQLObjectType); 69 | expect(fields.partial2type2.type).to.be.instanceof(gql.GraphQLObjectType); 70 | expect(fields.partial2String.type).to.equal(gql.GraphQLString); 71 | 72 | const type1 = fields.partial1type1.type; 73 | const type2 = fields.partial2type2.type; 74 | 75 | // check Type1 76 | fields = type1.getFields(); 77 | expect(Object.keys(fields)).to.deep.equal(['type2list']); 78 | expect(fields.type2list.type).to.be.instanceof(gql.GraphQLList); 79 | const lst = >fields.type2list.type; 80 | expect(lst.ofType).to.equal(type2, 'Expecting to have a list of Type2'); 81 | 82 | // check Type2 83 | fields = type2.getFields(); 84 | expect(Object.keys(fields)).to.deep.equal(['self', 'type1']); 85 | expect(fields.self.type).to.equal(type2); 86 | expect(fields.type1.type).to.equal(type1); 87 | }); 88 | 89 | 90 | 91 | class PartialExtension extends InversifyPartialMap { 92 | map() { 93 | return { 94 | extension: { type: gql.GraphQLString } 95 | } 96 | } 97 | } 98 | 99 | function testExtend(typeToExtend: string | inv.interfaces.Newable>) { 100 | 101 | builder.query.merge(PartialRoot1, PartialRoot2); 102 | builder.get(typeToExtend).merge(PartialExtension); 103 | const schema = builder.build(); 104 | const rootQuery = schema.getQueryType(); 105 | 106 | // check root type 107 | let fields = rootQuery.getFields(); 108 | const keys = Object.keys(fields); 109 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 110 | expect(keys).to.deep.equal(['partial1type1', 'deep', 'partial2type2', 'partial2String']) 111 | expect(fields.partial1type1.type).to.be.instanceof(gql.GraphQLObjectType); 112 | expect(fields.partial2String.type).to.equal(gql.GraphQLString); 113 | 114 | // check that Type1 has been extended 115 | const type1 = fields.partial1type1.type; 116 | fields = type1.getFields(); 117 | expect(Object.keys(fields)).to.deep.equal(['type2list', 'extension']); 118 | } 119 | 120 | it('lets you extend a type by name', () => { 121 | testExtend('Type1'); 122 | }); 123 | 124 | 125 | 126 | 127 | it('lets you extend a type by ctor', () => { 128 | testExtend(Type1); 129 | }); 130 | }); -------------------------------------------------------------------------------- /test/inversify-graphql-spec.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'mocha'; 3 | import { expect } from 'chai'; 4 | import * as inv from 'inversify'; 5 | import * as gql from 'graphql'; 6 | import {inversifySchema} from '../src'; 7 | import { SchemaBuilder, schemaDefinition, Dependency } from './types'; 8 | 9 | 10 | describe('graphql-inversify', () => { 11 | 12 | let container: inv.Container; 13 | beforeEach(() => { 14 | container = new inv.Container(); 15 | container.bind(Dependency).toSelf().inSingletonScope(); 16 | }) 17 | 18 | 19 | it('builds schema from object', () => { 20 | const schema = inversifySchema(container, schemaDefinition); 21 | const rootQuery = schema.getQueryType(); 22 | const fields = rootQuery.getFields(); 23 | const keys = Object.keys(fields); 24 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 25 | }) 26 | 27 | it('builds schema from builder', () => { 28 | const schema = inversifySchema(container, SchemaBuilder); 29 | const rootQuery = schema.getQueryType(); 30 | 31 | // check root type 32 | let fields = rootQuery.getFields(); 33 | const keys = Object.keys(fields); 34 | expect(keys.length).to.equal(4, 'Expecting 3 fields on root'); 35 | expect(keys).to.deep.equal(['partial1type1', 'deep', 'partial2type2', 'partial2String']) 36 | expect(fields.partial1type1.type).to.be.instanceof(gql.GraphQLObjectType); 37 | expect(fields.partial2type2.type).to.be.instanceof(gql.GraphQLObjectType); 38 | expect(fields.partial2String.type).to.equal(gql.GraphQLString); 39 | 40 | const type1 = fields.partial1type1.type; 41 | const type2 = fields.partial2type2.type; 42 | 43 | // check Type1 44 | fields = type1.getFields(); 45 | expect(Object.keys(fields)).to.deep.equal(['type2list']); 46 | expect(fields.type2list.type).to.be.instanceof(gql.GraphQLList); 47 | const lst = > fields.type2list.type; 48 | expect(lst.ofType).to.equal(type2, 'Expecting to have a list of Type2'); 49 | 50 | // check Type2 51 | fields = type2.getFields(); 52 | expect(Object.keys(fields)).to.deep.equal(['self', 'type1']); 53 | expect(fields.self.type).to.equal(type2); 54 | expect(fields.type1.type).to.equal(type1); 55 | 56 | }) 57 | 58 | 59 | 60 | it('builds deep object', () => { 61 | const schema = inversifySchema(container, schemaDefinition); 62 | const rootQuery = schema.getQueryType(); 63 | const fields = rootQuery.getFields(); 64 | const keys = Object.keys(fields); 65 | 66 | const t1 = fields.deep.type; 67 | const f1 = t1.getFields(); 68 | expect(Object.keys(f1)).to.deep.equal(['nested']); 69 | const t2 = f1.nested.type; 70 | const f2 = t2.getFields(); 71 | expect(Object.keys(f2)).to.deep.equal(['subnested']); 72 | const t3 = f2.subnested.type; 73 | const f3 = t3.getFields(); 74 | expect(Object.keys(f3)).to.deep.equal(['prop']); 75 | }) 76 | 77 | }); -------------------------------------------------------------------------------- /test/types.ts: -------------------------------------------------------------------------------- 1 | import * as inv from 'inversify'; 2 | import * as gql from 'graphql'; 3 | import * as igql from '../src'; 4 | // tslint:disable: no-use-before-declare 5 | 6 | @inv.injectable() 7 | export class Dependency { 8 | 9 | } 10 | 11 | 12 | export class SchemaBuilder extends igql.InversifySchemaBuilder { 13 | @inv.inject(Dependency) dep: Dependency; 14 | 15 | schema(): igql.InversifySchemaConfig { 16 | return schemaDefinition; 17 | } 18 | } 19 | 20 | 21 | export class RootQuery extends igql.InversifyObjectTypeBuilder { 22 | 23 | @inv.inject(Dependency) dep: Dependency; 24 | 25 | config(): igql.InversifyObjectConfig { 26 | return { 27 | name: 'Root', 28 | fields: [PartialRoot1, PartialRoot2], 29 | } 30 | } 31 | 32 | } 33 | 34 | export class PartialRoot1 extends igql.InversifyPartialMap { 35 | @inv.inject(Dependency) dep: Dependency; 36 | 37 | map(): gql.Thunk> { 38 | return { 39 | partial1type1: { type: Type1 }, 40 | deep: {type: DeepType} 41 | } 42 | } 43 | 44 | } 45 | export class PartialRoot2 extends igql.InversifyPartialMap { 46 | @inv.inject(Dependency) dep: Dependency; 47 | 48 | map(): gql.Thunk> { 49 | return () => ({ 50 | partial2type2: { type: Type2 }, 51 | partial2String: { type: gql.GraphQLString } 52 | }) 53 | } 54 | 55 | } 56 | 57 | 58 | export class Type1 extends igql.InversifyObjectTypeBuilder { 59 | @inv.inject(Dependency) dep: Dependency; 60 | 61 | config(): igql.InversifyObjectConfig { 62 | return { 63 | name: 'Type1', 64 | fields: { 65 | type2list: { type: igql.InversifyList(Type2) }, 66 | }, 67 | } 68 | } 69 | } 70 | 71 | 72 | export class Type2 extends igql.InversifyObjectTypeBuilder { 73 | @inv.inject(Dependency) dep: Dependency; 74 | 75 | config(): igql.InversifyObjectConfig { 76 | return { 77 | name: 'Type2', 78 | fields: () => ({ 79 | self: { type: Type2 }, 80 | type1: { type: Type1 }, 81 | }), 82 | } 83 | } 84 | } 85 | 86 | 87 | 88 | export class DeepType extends igql.InversifyObjectTypeBuilder { 89 | @inv.inject(Dependency) dep: Dependency; 90 | 91 | config(): igql.InversifyObjectConfig { 92 | return { 93 | name: 'DeepType', 94 | fields: { 95 | nested: { 96 | type: { 97 | name: 'NestedDeep', 98 | fields: { 99 | subnested: { 100 | type: { 101 | name: 'SubnestedDeep', 102 | fields: { 103 | prop: { type: gql.GraphQLString }, 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | }, 111 | } 112 | } 113 | } 114 | 115 | export const schemaDefinition: igql.InversifySchemaConfig = { 116 | query: RootQuery, 117 | }; -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | "types": [ 9 | "node", 10 | "reflect-metadata" 11 | ], 12 | "moduleResolution": "node", 13 | "experimentalDecorators": true, 14 | "emitDecoratorMetadata": true 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | ] 19 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "bin", 5 | "baseUrl": "src", 6 | "declaration": true 7 | }, 8 | "include": [ 9 | "src/**/*.ts", 10 | ] 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | "sample/**/*.ts", 6 | "test/**/*.ts" 7 | ], 8 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "arrow-return-shorthand": true, 4 | "callable-types": true, 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "deprecation": { 11 | "severity": "warn" 12 | }, 13 | "forin": true, 14 | "import-blacklist": [ 15 | true, 16 | "rxjs/Rx", 17 | "rxjs/internal/operators" 18 | ], 19 | "interface-over-type-literal": true, 20 | "label-position": true, 21 | "member-access": false, 22 | "member-ordering": [ 23 | true, 24 | { 25 | "order": [ 26 | "static-field", 27 | "instance-field", 28 | "static-method", 29 | "instance-method" 30 | ] 31 | } 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-super": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [ 50 | true, 51 | "ignore-params" 52 | ], 53 | "no-misused-new": true, 54 | "no-non-null-assertion": true, 55 | "no-shadowed-variable": true, 56 | "no-string-literal": false, 57 | "no-string-throw": true, 58 | "no-switch-case-fall-through": true, 59 | "no-unnecessary-initializer": true, 60 | "no-unused-expression": true, 61 | "no-use-before-declare": true, 62 | "no-var-keyword": true, 63 | "object-literal-sort-keys": false, 64 | "prefer-const": true, 65 | "quotemark": [ 66 | true, 67 | "single" 68 | ], 69 | "radix": true, 70 | "triple-equals": [ 71 | true, 72 | "allow-null-check" 73 | ], 74 | "unified-signatures": true, 75 | "variable-name": false, 76 | "whitespace": [ 77 | true, 78 | "check-branch", 79 | "check-decl", 80 | "check-operator", 81 | "check-separator", 82 | "check-type" 83 | ], 84 | "no-output-on-prefix": true, 85 | "use-input-property-decorator": true, 86 | "use-output-property-decorator": true, 87 | "use-host-property-decorator": true, 88 | "no-input-rename": true, 89 | "no-output-rename": true, 90 | "use-life-cycle-interface": true, 91 | "use-pipe-transform-interface": true, 92 | "component-class-suffix": true, 93 | "directive-class-suffix": true 94 | } 95 | } 96 | --------------------------------------------------------------------------------