├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package.json ├── resources └── jest-ts-transform.js ├── src ├── __tests__ │ ├── __snapshots__ │ │ ├── description-test.ts.snap │ │ └── fixtures-test.ts.snap │ ├── description-test.ts │ ├── fixtures-test.ts │ ├── fixtures │ │ ├── interface-fixture.ts │ │ ├── object-fixture.ts │ │ ├── scalar-fixture.ts │ │ ├── schema-fixture.ts │ │ └── wrap-fixture.ts │ ├── object-test.ts │ └── schema-test.ts ├── args.ts ├── description.ts ├── enum.ts ├── index.ts ├── interface.ts ├── list.ts ├── nullable.ts ├── object.ts ├── scalar.ts ├── schema.ts ├── type.ts └── wrap.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | yarn-debug.log 5 | build 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Caleb Meredith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strong GraphQL 2 | 3 | [![Package on npm](https://img.shields.io/npm/v/graphql-strong.svg?style=flat)](https://www.npmjs.com/package/graphql-strong) 4 | 5 | One of the biggest reasons to use [GraphQL](http://graphql.org/) is its static type system. This type system makes it easy for GraphQL clients to do interesting optimizations and allows for powerful developer tools. Including GraphQL API development tools. 6 | 7 | Strong GraphQL, or `graphql-strong`, is a library that leverages TypeScript types to give you the power of static type analysis when defining your GraphQL API in JavaScript. With the reference [`graphql-js`](github.com/graphql/graphql-js) implementation you can’t get the type safety GraphQL provides when building your API, but with Strong GraphQL you can. 8 | 9 | ## Installation 10 | Strong GraphQL requires GraphQL-JS, TypeScript version 2.1 or higher, and Node.js 6.x or higher. 11 | 12 | It is recommended that you set `noImplicitAny` to `true` and `strictNullChecks` to `true` in your TypeScript configuration to get the most accuracy possible from Strong GraphQL. 13 | 14 | It is also recommended that you use [Visual Studio Code](https://code.visualstudio.com/) for the best developer experience possible. 15 | 16 | You can use Strong GraphQL without TypeScript, however then you lose the strong typing guarantees that makes Strong GraphQL useful. Strong GraphQL depends on some TypeScript features and so does not have Flow types. 17 | 18 | ```bash 19 | npm install graphql --save 20 | npm install graphql-strong --save 21 | npm install typescript --save-dev 22 | npm install @types/graphql --save-dev 23 | ``` 24 | 25 | ## Example 26 | In the below example, we define the `Image` and `Person` GraphQL types. 27 | 28 | ```ts 29 | import { createObjectType, StringType, FloatType, IntegerType } from 'graphql-strong' 30 | 31 | interface Image { 32 | url: string 33 | width?: number 34 | height?: number 35 | } 36 | 37 | const ImageType = createObjectType({ 38 | name: 'Image', 39 | }) 40 | .fieldNonNull({ 41 | name: 'url', 42 | type: StringType, 43 | resolve: ({ url }) => url, 44 | }) 45 | .field({ 46 | name: 'width', 47 | type: FloatType, 48 | resolve: ({ width }) => width, 49 | }) 50 | .field({ 51 | name: 'height', 52 | type: FloatType, 53 | resolve: ({ height }) => height, 54 | }) 55 | 56 | interface Person { 57 | name: string 58 | email: string 59 | imageURL?: string 60 | } 61 | 62 | const PersonType = createObjectType({ 63 | name: 'Person', 64 | }) 65 | .fieldNonNull({ 66 | name: 'name', 67 | type: StringType, 68 | resolve: ({ name }) => name, 69 | }) 70 | .fieldNonNull({ 71 | name: 'email', 72 | type: StringType, 73 | resolve: ({ email }) => email, 74 | }) 75 | .field({ 76 | name: 'image', 77 | type: ImageType, 78 | args: { 79 | width: { 80 | type: IntegerType, 81 | }, 82 | height: { 83 | type: IntegerType.nullable(), 84 | }, 85 | }, 86 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 87 | url: imageURL, 88 | width, 89 | height, 90 | } : null, 91 | }) 92 | ``` 93 | 94 | Which in the GraphQL schema definition will look like: 95 | 96 | ```graphql 97 | type Image { 98 | url: String! 99 | width: Float 100 | height: Float 101 | } 102 | 103 | type Person { 104 | name: String! 105 | email: String! 106 | image(width: Float!, height: Float): Image 107 | } 108 | ``` 109 | 110 | Nice! But the real power of Strong GraphQL is in catching code *that should not work*. 111 | 112 | ```ts 113 | const ImageType = createObjectType({ 114 | name: 'Image', 115 | }) 116 | .fieldNonNull({ 117 | name: 'url', 118 | type: FloatType, // Error: expected a `StringType`. 119 | resolve: () => null, // Error: expected a `string` to be returned, not `null`. 120 | }) 121 | .field({ 122 | name: 'size', 123 | type: FloatType, 124 | resolve: ({ doesNotExist }) => doesNotExist, // Error: could not find property `doesNotExist` on type `Image`. 125 | }) 126 | 127 | const PersonType = createObjectType({ 128 | name: 'Person', 129 | }) 130 | .field({ 131 | name: 'image', 132 | type: ImageType, 133 | args: { // Error: missing `height` argument definition, only found `width`. 134 | width: { 135 | type: IntegerType.nullable(), // Error: `width` type should not be nullable. 136 | }, 137 | }, 138 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 139 | url: imageURL, 140 | width, 141 | height, 142 | } : null, 143 | }) 144 | ``` 145 | 146 | The object types you create with Strong GraphQL are also interoperable with GraphQL-JS objects. 147 | 148 | ```ts 149 | import { GraphQLObjectType, GraphQLID } from 'graphql' 150 | 151 | const QueryType = new GraphQLObjectType({ 152 | name: 'Query', 153 | fields: { 154 | personByID: { 155 | type: PersonType, // <-- This is the same type we defined above with Strong GraphQL. 156 | args: { 157 | id: { type: GraphQLID }, 158 | }, 159 | resolve: () => { ... }, 160 | }, 161 | }, 162 | }) 163 | ``` 164 | 165 | ## API 166 | Before we dive into all of the Strong GraphQL functions, let’s take a moment to understand how the type system is set up. 167 | 168 | All Strong GraphQL Types inherit from one of three interfaces: `StrongGraphQLInputType` like an input object type, `StrongGraphQLOutputType`s like an object or union type, and `StrongGraphQLInputOutputType`s like an enum or scalar type. 169 | 170 | Input and output types respectively may only be used in certain locations, but input-output types can be used in either input or output locations. 171 | 172 | The `TValue` generic type associated with all Strong GraphQL Types is used for type checking. It represents the type of a value at runtime. 173 | 174 | So for example the `StringType` variable has a type of `StrongGraphQLInputOutputType` which means it can be used as either an input or an output type, and at runtime its type is a string. 175 | 176 | Now, on to explaining what each function does: 177 | 178 | ### `createObjectType(config): StrongGraphQLObjectType` 179 | Creates a Strong GraphQL Object Type which you can add fields to using a chaining API. The `StrongGraphQLObjectType` that `createObjectType` returns extends the GraphQL-JS `GraphQLObjectType` class and so inherits all of that class’s methods. This type will be non-null. 180 | 181 | The first type argument, `TValue`, will be the type of the first argument in the resolve function for your fields. 182 | 183 | The second type argument, `TContext`, is optional and will be used as the type for the context in your field resolve functions. If not provided this type defaults to the empty object type: `{}`. 184 | 185 | The first argument, `config`, can take the following properties: 186 | 187 | - `name: string`: The required name of your type. 188 | - `description?: string`: An optional description of your type. 189 | - `isTypeOf: (value: any, context: TContext) => value is TValue`: A function you define which will verify if a value is indeed of the type you created. 190 | 191 | Examples: 192 | 193 | ```ts 194 | const ImageType = createObjectType({ 195 | name: 'Image', 196 | description: 'A visual representation of a subject that can be found somewhere on the internet.', 197 | }) 198 | 199 | const PersonType = createObjectType({ 200 | name: 'Person', 201 | isTypeOf: value => value instanceof Person, 202 | }) 203 | ``` 204 | 205 | ### `StrongGraphQLObjectType#field(config): StrongGraphQLObjectType` 206 | Returns a *new* Strong GraphQL Object Type with a new field added on. It is important to remember that this function does not mutate your type, but instead returns a new one. 207 | 208 | This method always creates fields with a nullable type. This is because you can always make a field non-null without introducing a breaking change into your API, but you can never take a non-null field and make it nullable unless you want to introduce a breaking change. To create a field that is non-null use `fieldNonNull`. 209 | 210 | The first type argument, `TFieldValue`, is the type of the field being resolved. 211 | 212 | The second optional type argument, `TArgs`, is the type of the arguments we expect for this field. By default it is the empty object type: `{}`. 213 | 214 | The first argument, `config`, can take the following properties: 215 | 216 | - `name: string`: The required name of this field. If the name is not unique, an error will be thrown. 217 | - `description?: string`: The optional description of the field. 218 | - `deprecationReason?: string`: If you chose to deprecate this field, this is the optional reason why. 219 | - `type: StrongGraphQLOutputType | (() => StrongGraphQLOutputType)`: The type of the field. Note that the type may be any `StrongGraphQLOutputType`, or a function that returns a `StrongGraphQLOutputType`. This allows you to use recursive types. Also note that whatever `StrongGraphQLOutputType` you use, it’s type must match up with the field’s type. This type will automatically be converted into a nullable type. If you do not want this type to automatically be converted into a nullable type use `fieldNonNull` instead. 220 | - `args?: StrongGraphQLArgsConfig`: An argument definition map which matches your `TArgs` type. We will go into this more below. 221 | - `resolve: (source: TValue, args: TArgs, context: TContext) => TFieldValue`: Your resolver function. It takes the object value, arguments, and context as parameters and returns the field value. This function may also return null. If your field *always* returns a value, use `fieldNonNull`. 222 | 223 | Examples: 224 | 225 | ```ts 226 | PersonType 227 | .field({ 228 | name: 'image', 229 | description: 'Gets the image for our person with a provided width and height in pixels.', 230 | type: ImageType, 231 | args: { 232 | width: { 233 | type: IntegerType, 234 | description: 'The width of our image in pixels.', 235 | }, 236 | height: { 237 | type: IntegerType, 238 | description: 'The height of our image in pixels.', 239 | }, 240 | }, 241 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 242 | url: imageURL, 243 | width, 244 | height, 245 | } : null, 246 | }) 247 | ``` 248 | 249 | #### Arguments 250 | The `args` property on your field config must *always* match the type you provided with `TArgs`. For instance, say you have arguments that look like this: 251 | 252 | ```ts 253 | type Args = { 254 | width: number, 255 | height: number | null, 256 | } 257 | ``` 258 | 259 | Then your `args` property *must* have a non-null number `width` definition and a nullable number `height` definition. Like so: 260 | 261 | ```ts 262 | config.args = { 263 | width: { 264 | type: IntegerType, 265 | }, 266 | height: { 267 | type: IntegerType.nullable(), 268 | }, 269 | } 270 | ``` 271 | 272 | If you change the type of one of your arguments, say to `StringType`, an error will be generated: 273 | 274 | ```ts 275 | config.args = { 276 | width: { 277 | type: StringType, // Error: expected a type matching `number` here, not `StringType`. 278 | }, 279 | height: { 280 | type: IntegerType.nullable(), 281 | }, 282 | } 283 | ``` 284 | 285 | Your argument definitions may have the following properties: 286 | 287 | - `type: StrongGraphQLInputType`: The input type whose `TArgValue` must match the type in your `TArgs` type at the same property. 288 | - `defaultValue?: TArgValue`: An optional default value for this argument. 289 | - `description?: string`: An optional description of what this argument does. 290 | 291 | ### `StrongGraphQLObjectType#fieldNonNull(config): StrongGraphQLObjectType` 292 | Just like `StrongGraphQLObjectType#field` except while `StrongGraphQLObjectType#field` will *always* create a field with a nullable type, this field will allow you to create a field with a non-null type. 293 | 294 | It takes all the same arguments as `StrongGraphQLObjectType#field`. 295 | 296 | Example: 297 | 298 | ```ts 299 | const PersonType = createObjectType({ 300 | name: 'Person', 301 | }) 302 | .fieldNonNull({ 303 | name: 'name', 304 | type: StringType, 305 | resolve: ({ name }) => name, 306 | }) 307 | ``` 308 | 309 | Will become: 310 | 311 | ```graphql 312 | type Person { 313 | name: String! 314 | } 315 | ``` 316 | 317 | Whereas: 318 | 319 | ```ts 320 | const PersonType = createObjectType({ 321 | name: 'Person', 322 | }) 323 | .field({ 324 | name: 'name', 325 | type: StringType, 326 | resolve: ({ name }) => name, 327 | }) 328 | ``` 329 | 330 | Becomes: 331 | 332 | ```graphql 333 | type Person { 334 | name: String 335 | } 336 | ``` 337 | 338 | The difference between `String!` and `String` is actually pretty huge. Make sure you know when a type needs to be non-null and when it doesn’t. 339 | 340 | ### `StrongGraphQLType#nullable(): StrongGraphQLType` 341 | Every Strong GraphQL Type is non-null by default. This makes sense in a strongly-typed environment. To get the nullable variant of a type, just call `.nullable()`. This will return a new nullable type without mutating the type you called it on. 342 | 343 | Calling `nullable` on a nullable type will just return the type. 344 | 345 | Examples: 346 | 347 | ```ts 348 | PersonType.nullable() 349 | StringType.nullable() 350 | FloatType.nullable() 351 | ``` 352 | 353 | ### Disclaimer 354 | Strong GraphQL is currently in an expiremental phase. It does not support all GraphQL features, and not all of the features it does support are documented! 355 | 356 | If you are interested in using Strong GraphQL *let the author know*. You can do so by opening issues on this repo or sending tweets to [@calebmer](http://twitter.com/calebmer). 357 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-strong", 3 | "version": "0.8.0", 4 | "description": "Define your GraphQL schemas with confidence that your values are correct.", 5 | "author": "Caleb Meredith ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/calebmer/graphql-strong#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/calebmer/graphql-strong.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/calebmer/graphql-strong/issues" 14 | }, 15 | "keywords": [ 16 | "graphql", 17 | "typescript", 18 | "schema", 19 | "strong", 20 | "definition", 21 | "define", 22 | "api", 23 | "types", 24 | "typecheck" 25 | ], 26 | "main": "build/index.js", 27 | "typings": "build/index.d.ts", 28 | "files": [ 29 | "package.json", 30 | "README.md", 31 | "LICENSE", 32 | "build" 33 | ], 34 | "scripts": { 35 | "preversion": "npm run ci", 36 | "prepublish": "npm run build", 37 | "lint": "tslint 'src/**/*.ts'", 38 | "test": "jest", 39 | "test-watch": "jest --watch", 40 | "ci": "npm run lint && npm run test", 41 | "build": "rm -rf build && tsc" 42 | }, 43 | "peerDependencies": { 44 | "graphql": ">=0.6.0 <1.0.0" 45 | }, 46 | "devDependencies": { 47 | "@types/graphql": "^0.8.2", 48 | "@types/jest": "^16.0.1", 49 | "@types/node": "^6.0.52", 50 | "graphql": "^0.8.2", 51 | "jest": "^19.0.2", 52 | "tslint": "^4.5.1", 53 | "typescript": "^2.2.1" 54 | }, 55 | "jest": { 56 | "roots": [ 57 | "/src" 58 | ], 59 | "moduleFileExtensions": [ 60 | "js", 61 | "ts" 62 | ], 63 | "transform": { 64 | ".ts": "/resources/jest-ts-transform.js" 65 | }, 66 | "testRegex": "/__tests__/[^.]+-test.ts$", 67 | "browser": false, 68 | "testEnvironment": "node" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /resources/jest-ts-transform.js: -------------------------------------------------------------------------------- 1 | const tsc = require('typescript') 2 | const tsConfig = require('../tsconfig.json') 3 | 4 | const { compilerOptions } = tsConfig 5 | 6 | module.exports = { 7 | process (src, fileName) { 8 | // Make sure that source maps are always enabled and inline so that they 9 | // can be picked up by the `source-map-support` package. 10 | return tsc.transpileModule(src, { compilerOptions, fileName }).outputText 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/description-test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`trimDescription will correctly format a multiline string into a description 1`] = ` 4 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquam dapibus 5 | diam eu convallis. Nullam tincidunt mi quis purus congue, eu commodo eros 6 | dapibus. Vivamus maximus ex nisi, sit amet varius dui vestibulum et. Duis in 7 | sem eros. Suspendisse libero ipsum, vulputate sed tortor placerat, convallis 8 | porta tortor. Etiam fringilla iaculis venenatis. Donec vitae mauris quis dui 9 | convallis ullamcorper. Nulla a velit neque. Suspendisse ac tincidunt lacus. 10 | Vivamus sit amet rhoncus dolor, id laoreet neque. 11 | 12 | Duis vulputate, dolor in dictum faucibus, velit nibh laoreet sem, a vehicula 13 | lorem felis a eros. Nulla tortor dolor, imperdiet vel enim eget, gravida 14 | dapibus ante. Cras fringilla erat at tortor cursus auctor. Donec elementum 15 | lacus malesuada, lobortis tellus eleifend, posuere velit. Sed neque sem, 16 | pharetra quis tellus eget, ornare hendrerit velit. Vivamus in urna eu libero 17 | vulputate dapibus. Nunc rhoncus luctus lacus, ac molestie augue finibus ut. 18 | Phasellus lobortis fermentum justo, id consectetur nisi ultrices nec. 19 | 20 | 1. Nunc quis lacus semper, vehicula magna nec, aliquet magna. 21 | 2. Suspendisse consequat nunc in vehicula elementum. 22 | 3. Aenean et metus in enim ultricies elementum. 23 | 4. Suspendisse laoreet turpis eu nisl tempus vestibulum. 24 | 25 | Pellentesque viverra efficitur magna ut malesuada. Nunc aliquet luctus 26 | convallis. Sed ac nulla arcu. Duis felis dui, placerat et enim quis, 27 | elementum dapibus quam. Mauris sem nulla, suscipit ut dolor a, tempus varius 28 | ligula. Ut hendrerit ultrices orci, vel rutrum purus convallis tincidunt. 29 | Sed varius arcu ullamcorper, ultrices turpis non, tempus est. Morbi et 30 | tortor ac risus lacinia malesuada sit amet ac nisl. Nulla fringilla elit nec 31 | tellus euismod, eu cursus ipsum ultricies. Etiam fermentum vestibulum felis, 32 | ac feugiat velit facilisis at. 33 | 34 | - Cras dapibus sem id vulputate commodo. 35 | - Ut viverra ipsum eget sem dignissim porttitor." 36 | `; 37 | 38 | exports[`trimDescriptionsInConfig will trim descriptions in nested objects 1`] = ` 39 | Object { 40 | "a": 1, 41 | "b": 2, 42 | "c": Object { 43 | "d": Object { 44 | "description": "Have you ever been down the water spout? 45 | To the very bottom of the water system? 46 | There you will find a little aligator 47 | He goes by the name of Alfred if you do he’s mine. 48 | I lost him. 49 | I threw him down the water spout 50 | and now I’m very lonely ‘cuz he’s gone. 51 | I miss him.", 52 | "e": 3, 53 | "f": 4, 54 | "g": Object { 55 | "h": 5, 56 | "i": 6, 57 | }, 58 | "j": NotPlainObject { 59 | "description": " Space allowed!!! ", 60 | "k": 7, 61 | "l": 8, 62 | }, 63 | }, 64 | }, 65 | "description": "Hello, world!", 66 | } 67 | `; 68 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/fixtures-test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`interface-fixture.ts 1`] = ` 4 | Array [ 5 | Object { 6 | "category": 1, 7 | "code": 2345, 8 | "length": 159, 9 | "messageText": Object { 10 | "category": 1, 11 | "code": 2345, 12 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { foo: { type: StrongInputOutputType; }; bar: {...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { foo: { type: string; }; bar: { type: string | null | undefined; }...'.", 13 | "next": Object { 14 | "category": 1, 15 | "code": 2326, 16 | "messageText": "Types of property 'fields' are incompatible.", 17 | "next": Object { 18 | "category": 1, 19 | "code": 2322, 20 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType; }' is not assignable to type 'StrongInterfaceFieldConfig'.", 29 | "next": Object { 30 | "category": 1, 31 | "code": 2326, 32 | "messageText": "Types of property 'type' are incompatible.", 33 | "next": Object { 34 | "category": 1, 35 | "code": 2322, 36 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType'.", 37 | "next": Object { 38 | "category": 1, 39 | "code": 2326, 40 | "messageText": "Types of property 'nullable' are incompatible.", 41 | "next": Object { 42 | "category": 1, 43 | "code": 2322, 44 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongOutputType'.", 45 | "next": Object { 46 | "category": 1, 47 | "code": 2322, 48 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType'.", 49 | "next": Object { 50 | "category": 1, 51 | "code": 2326, 52 | "messageText": "Types of property 'nullable' are incompatible.", 53 | "next": Object { 54 | "category": 1, 55 | "code": 2322, 56 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongOutputType'.", 57 | "next": Object { 58 | "category": 1, 59 | "code": 2322, 60 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType'.", 61 | "next": undefined, 62 | }, 63 | }, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }, 71 | }, 72 | }, 73 | }, 74 | }, 75 | "start": 589, 76 | "text": "{ 77 | name: 'Foobar', 78 | resolveType: undefined as any, 79 | fields: { 80 | foo: { type: IntegerType }, 81 | bar: { type: StringType.nullable() }, 82 | }, 83 | }", 84 | }, 85 | Object { 86 | "category": 1, 87 | "code": 2345, 88 | "length": 159, 89 | "messageText": Object { 90 | "category": 1, 91 | "code": 2345, 92 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { foo: { type: StrongInputOutputType; }; bar: {...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { foo: { type: string; }; bar: { type: string | null | undefined; }...'.", 93 | "next": Object { 94 | "category": 1, 95 | "code": 2326, 96 | "messageText": "Types of property 'fields' are incompatible.", 97 | "next": Object { 98 | "category": 1, 99 | "code": 2322, 100 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType; }' is not assignable to type 'StrongInterfaceFieldConfig'.", 109 | "next": Object { 110 | "category": 1, 111 | "code": 2326, 112 | "messageText": "Types of property 'type' are incompatible.", 113 | "next": Object { 114 | "category": 1, 115 | "code": 2322, 116 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType'.", 117 | "next": undefined, 118 | }, 119 | }, 120 | }, 121 | }, 122 | }, 123 | }, 124 | }, 125 | "start": 891, 126 | "text": "{ 127 | name: 'Foobar', 128 | resolveType: undefined as any, 129 | fields: { 130 | foo: { type: StringType }, 131 | bar: { type: IntegerType.nullable() }, 132 | }, 133 | }", 134 | }, 135 | Object { 136 | "category": 1, 137 | "code": 2345, 138 | "length": 114, 139 | "messageText": Object { 140 | "category": 1, 141 | "code": 2345, 142 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { foo: { type: StrongInputOutputType; }; }; }' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { foo: { type: string; }; bar: { type: string | null | undefined; }...'.", 143 | "next": Object { 144 | "category": 1, 145 | "code": 2326, 146 | "messageText": "Types of property 'fields' are incompatible.", 147 | "next": Object { 148 | "category": 1, 149 | "code": 2322, 150 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongInterfaceFieldMapConfig<{ foo: { type: string; }; bar: { type: string | null | undefined; }...'.", 151 | "next": Object { 152 | "category": 1, 153 | "code": 2324, 154 | "messageText": "Property 'bar' is missing in type '{ foo: { type: StrongInputOutputType; }; }'.", 155 | "next": undefined, 156 | }, 157 | }, 158 | }, 159 | }, 160 | "start": 1192, 161 | "text": "{ 162 | name: 'Foobar', 163 | resolveType: undefined as any, 164 | fields: { 165 | foo: { type: StringType }, 166 | }, 167 | }", 168 | }, 169 | Object { 170 | "category": 1, 171 | "code": 2345, 172 | "length": 125, 173 | "messageText": Object { 174 | "category": 1, 175 | "code": 2345, 176 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { bar: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongInterfaceFieldMapConfig<{ foo: { type: string; }; bar: { type: string | null | undefined; }...'.", 185 | "next": Object { 186 | "category": 1, 187 | "code": 2324, 188 | "messageText": "Property 'foo' is missing in type '{ bar: { type: StrongInputOutputType; }; }'.", 189 | "next": undefined, 190 | }, 191 | }, 192 | }, 193 | }, 194 | "start": 1448, 195 | "text": "{ 196 | name: 'Foobar', 197 | resolveType: undefined as any, 198 | fields: { 199 | bar: { type: StringType.nullable() }, 200 | }, 201 | }", 202 | }, 203 | Object { 204 | "category": 1, 205 | "code": 2345, 206 | "length": 240, 207 | "messageText": Object { 208 | "category": 1, 209 | "code": 2345, 210 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { x: { type: StrongInputOutputType; args: { foo...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { x: { type: string; args: { foo: string; bar: string | null | unde...'.", 211 | "next": Object { 212 | "category": 1, 213 | "code": 2326, 214 | "messageText": "Types of property 'fields' are incompatible.", 215 | "next": Object { 216 | "category": 1, 217 | "code": 2322, 218 | "messageText": "Type '{ x: { type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }...' is not assignable to type 'StrongInterfaceFieldMapConfig<{ x: { type: string; args: { foo: string; bar: string | null | unde...'.", 219 | "next": Object { 220 | "category": 1, 221 | "code": 2326, 222 | "messageText": "Types of property 'x' are incompatible.", 223 | "next": Object { 224 | "category": 1, 225 | "code": 2322, 226 | "messageText": "Type '{ type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }; bar...' is not assignable to type 'StrongInterfaceFieldConfig<{ foo: string; bar: string | null | undefined; }, string>'.", 227 | "next": Object { 228 | "category": 1, 229 | "code": 2326, 230 | "messageText": "Types of property 'args' are incompatible.", 231 | "next": Object { 232 | "category": 1, 233 | "code": 2322, 234 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType | undefined'.", 235 | "next": Object { 236 | "category": 1, 237 | "code": 2322, 238 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType'.", 239 | "next": Object { 240 | "category": 1, 241 | "code": 2326, 242 | "messageText": "Types of property 'foo' are incompatible.", 243 | "next": Object { 244 | "category": 1, 245 | "code": 2322, 246 | "messageText": "Type '{ type: StrongInputOutputType; }' is not assignable to type 'StrongArgConfig'.", 247 | "next": Object { 248 | "category": 1, 249 | "code": 2326, 250 | "messageText": "Types of property 'type' are incompatible.", 251 | "next": Object { 252 | "category": 1, 253 | "code": 2322, 254 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 255 | "next": Object { 256 | "category": 1, 257 | "code": 2326, 258 | "messageText": "Types of property 'nullable' are incompatible.", 259 | "next": Object { 260 | "category": 1, 261 | "code": 2322, 262 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongInputType'.", 263 | "next": Object { 264 | "category": 1, 265 | "code": 2322, 266 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 267 | "next": Object { 268 | "category": 1, 269 | "code": 2326, 270 | "messageText": "Types of property 'nullable' are incompatible.", 271 | "next": Object { 272 | "category": 1, 273 | "code": 2322, 274 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongInputType'.", 275 | "next": Object { 276 | "category": 1, 277 | "code": 2322, 278 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 279 | "next": undefined, 280 | }, 281 | }, 282 | }, 283 | }, 284 | }, 285 | }, 286 | }, 287 | }, 288 | }, 289 | }, 290 | }, 291 | }, 292 | }, 293 | }, 294 | }, 295 | }, 296 | }, 297 | }, 298 | "start": 2192, 299 | "text": "{ 300 | name: 'Foobar', 301 | resolveType: undefined as any, 302 | fields: { 303 | x: { 304 | type: StringType, 305 | args: { 306 | foo: { type: IntegerType }, 307 | bar: { type: StringType.nullable() }, 308 | }, 309 | }, 310 | }, 311 | }", 312 | }, 313 | Object { 314 | "category": 1, 315 | "code": 2345, 316 | "length": 240, 317 | "messageText": Object { 318 | "category": 1, 319 | "code": 2345, 320 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { x: { type: StrongInputOutputType; args: { foo...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { x: { type: string; args: { foo: string; bar: string | null | unde...'.", 321 | "next": Object { 322 | "category": 1, 323 | "code": 2326, 324 | "messageText": "Types of property 'fields' are incompatible.", 325 | "next": Object { 326 | "category": 1, 327 | "code": 2322, 328 | "messageText": "Type '{ x: { type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }...' is not assignable to type 'StrongInterfaceFieldMapConfig<{ x: { type: string; args: { foo: string; bar: string | null | unde...'.", 329 | "next": Object { 330 | "category": 1, 331 | "code": 2326, 332 | "messageText": "Types of property 'x' are incompatible.", 333 | "next": Object { 334 | "category": 1, 335 | "code": 2322, 336 | "messageText": "Type '{ type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }; bar...' is not assignable to type 'StrongInterfaceFieldConfig<{ foo: string; bar: string | null | undefined; }, string>'.", 337 | "next": Object { 338 | "category": 1, 339 | "code": 2326, 340 | "messageText": "Types of property 'args' are incompatible.", 341 | "next": Object { 342 | "category": 1, 343 | "code": 2322, 344 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType | undefined'.", 345 | "next": Object { 346 | "category": 1, 347 | "code": 2322, 348 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; bar: { type: StrongInputOutputType'.", 349 | "next": Object { 350 | "category": 1, 351 | "code": 2326, 352 | "messageText": "Types of property 'bar' are incompatible.", 353 | "next": Object { 354 | "category": 1, 355 | "code": 2322, 356 | "messageText": "Type '{ type: StrongInputOutputType; }' is not assignable to type 'StrongArgConfig'.", 357 | "next": Object { 358 | "category": 1, 359 | "code": 2326, 360 | "messageText": "Types of property 'type' are incompatible.", 361 | "next": Object { 362 | "category": 1, 363 | "code": 2322, 364 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 365 | "next": undefined, 366 | }, 367 | }, 368 | }, 369 | }, 370 | }, 371 | }, 372 | }, 373 | }, 374 | }, 375 | }, 376 | }, 377 | }, 378 | "start": 2620, 379 | "text": "{ 380 | name: 'Foobar', 381 | resolveType: undefined as any, 382 | fields: { 383 | x: { 384 | type: StringType, 385 | args: { 386 | foo: { type: StringType }, 387 | bar: { type: IntegerType.nullable() }, 388 | }, 389 | }, 390 | }, 391 | }", 392 | }, 393 | Object { 394 | "category": 1, 395 | "code": 2345, 396 | "length": 191, 397 | "messageText": Object { 398 | "category": 1, 399 | "code": 2345, 400 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { x: { type: StrongInputOutputType; args: { foo...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { x: { type: string; args: { foo: string; bar: string | null | unde...'.", 401 | "next": Object { 402 | "category": 1, 403 | "code": 2326, 404 | "messageText": "Types of property 'fields' are incompatible.", 405 | "next": Object { 406 | "category": 1, 407 | "code": 2322, 408 | "messageText": "Type '{ x: { type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }...' is not assignable to type 'StrongInterfaceFieldMapConfig<{ x: { type: string; args: { foo: string; bar: string | null | unde...'.", 409 | "next": Object { 410 | "category": 1, 411 | "code": 2326, 412 | "messageText": "Types of property 'x' are incompatible.", 413 | "next": Object { 414 | "category": 1, 415 | "code": 2322, 416 | "messageText": "Type '{ type: StrongInputOutputType; args: { foo: { type: StrongInputOutputType; }; }; }' is not assignable to type 'StrongInterfaceFieldConfig<{ foo: string; bar: string | null | undefined; }, string>'.", 417 | "next": Object { 418 | "category": 1, 419 | "code": 2326, 420 | "messageText": "Types of property 'args' are incompatible.", 421 | "next": Object { 422 | "category": 1, 423 | "code": 2322, 424 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ foo: string; bar: string | null | undefined; }> | undefined'.", 425 | "next": Object { 426 | "category": 1, 427 | "code": 2322, 428 | "messageText": "Type '{ foo: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ foo: string; bar: string | null | undefined; }>'.", 429 | "next": Object { 430 | "category": 1, 431 | "code": 2324, 432 | "messageText": "Property 'bar' is missing in type '{ foo: { type: StrongInputOutputType; }; }'.", 433 | "next": undefined, 434 | }, 435 | }, 436 | }, 437 | }, 438 | }, 439 | }, 440 | }, 441 | }, 442 | }, 443 | "start": 3047, 444 | "text": "{ 445 | name: 'Foobar', 446 | resolveType: undefined as any, 447 | fields: { 448 | x: { 449 | type: StringType, 450 | args: { 451 | foo: { type: StringType }, 452 | }, 453 | }, 454 | }, 455 | }", 456 | }, 457 | Object { 458 | "category": 1, 459 | "code": 2345, 460 | "length": 202, 461 | "messageText": Object { 462 | "category": 1, 463 | "code": 2345, 464 | "messageText": "Argument of type '{ name: string; resolveType: any; fields: { x: { type: StrongInputOutputType; args: { bar...' is not assignable to parameter of type 'StrongInterfaceTypeConfig<{}, { x: { type: string; args: { foo: string; bar: string | null | unde...'.", 465 | "next": Object { 466 | "category": 1, 467 | "code": 2326, 468 | "messageText": "Types of property 'fields' are incompatible.", 469 | "next": Object { 470 | "category": 1, 471 | "code": 2322, 472 | "messageText": "Type '{ x: { type: StrongInputOutputType; args: { bar: { type: StrongInputOutputType; args: { bar: { type: StrongInputOutputType'.", 481 | "next": Object { 482 | "category": 1, 483 | "code": 2326, 484 | "messageText": "Types of property 'args' are incompatible.", 485 | "next": Object { 486 | "category": 1, 487 | "code": 2322, 488 | "messageText": "Type '{ bar: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ foo: string; bar: string | null | undefined; }> | undefined'.", 489 | "next": Object { 490 | "category": 1, 491 | "code": 2322, 492 | "messageText": "Type '{ bar: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ foo: string; bar: string | null | undefined; }>'.", 493 | "next": Object { 494 | "category": 1, 495 | "code": 2324, 496 | "messageText": "Property 'foo' is missing in type '{ bar: { type: StrongInputOutputType; }; }'.", 497 | "next": undefined, 498 | }, 499 | }, 500 | }, 501 | }, 502 | }, 503 | }, 504 | }, 505 | }, 506 | }, 507 | "start": 3425, 508 | "text": "{ 509 | name: 'Foobar', 510 | resolveType: undefined as any, 511 | fields: { 512 | x: { 513 | type: StringType, 514 | args: { 515 | bar: { type: StringType.nullable() }, 516 | }, 517 | }, 518 | }, 519 | }", 520 | }, 521 | Object { 522 | "category": 1, 523 | "code": 2345, 524 | "length": 6, 525 | "messageText": Object { 526 | "category": 1, 527 | "code": 2345, 528 | "messageText": "Argument of type '{}' is not assignable to parameter of type 'StrongInterfaceImplementation'.", 529 | "next": Object { 530 | "category": 1, 531 | "code": 2324, 532 | "messageText": "Property 'name' is missing in type '{}'.", 533 | "next": undefined, 534 | }, 535 | }, 536 | "start": 4446, 537 | "text": "{ 538 | 539 | }", 540 | }, 541 | Object { 542 | "category": 1, 543 | "code": 7031, 544 | "length": 5, 545 | "messageText": "Binding element 'email' implicitly has an 'any' type.", 546 | "start": 4529, 547 | "text": "email", 548 | }, 549 | Object { 550 | "category": 1, 551 | "code": 2345, 552 | "length": 25, 553 | "messageText": Object { 554 | "category": 1, 555 | "code": 2345, 556 | "messageText": "Argument of type '{ name: () => number; }' is not assignable to parameter of type 'StrongInterfaceImplementation'.", 557 | "next": Object { 558 | "category": 1, 559 | "code": 2326, 560 | "messageText": "Types of property 'name' are incompatible.", 561 | "next": Object { 562 | "category": 1, 563 | "code": 2322, 564 | "messageText": "Type '() => number' is not assignable to type 'StrongInterfaceFieldImplementation'.", 565 | "next": Object { 566 | "category": 1, 567 | "code": 2322, 568 | "messageText": "Type 'number' is not assignable to type 'string | Promise'.", 569 | "next": undefined, 570 | }, 571 | }, 572 | }, 573 | }, 574 | "start": 4883, 575 | "text": "{ 576 | name: () => 42, 577 | }", 578 | }, 579 | Object { 580 | "category": 1, 581 | "code": 7031, 582 | "length": 5, 583 | "messageText": "Binding element 'email' implicitly has an 'any' type.", 584 | "start": 4985, 585 | "text": "email", 586 | }, 587 | ] 588 | `; 589 | 590 | exports[`object-fixture.ts 1`] = ` 591 | Array [ 592 | Object { 593 | "category": 1, 594 | "code": 2345, 595 | "length": 74, 596 | "messageText": Object { 597 | "category": 1, 598 | "code": 2345, 599 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; resolve: ({url}: Image) => string; }' is not assignable to parameter of type 'StrongFieldConfig'.", 600 | "next": Object { 601 | "category": 1, 602 | "code": 2326, 603 | "messageText": "Types of property 'type' are incompatible.", 604 | "next": Object { 605 | "category": 1, 606 | "code": 2322, 607 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType | (() => StrongOutputType)'.", 608 | "next": Object { 609 | "category": 1, 610 | "code": 2322, 611 | "messageText": "Type 'StrongInputOutputType' is not assignable to type '() => StrongOutputType'.", 612 | "next": Object { 613 | "category": 1, 614 | "code": 2658, 615 | "messageText": "Type 'StrongInputOutputType' provides no match for the signature '(): StrongOutputType'", 616 | "next": undefined, 617 | }, 618 | }, 619 | }, 620 | }, 621 | }, 622 | "start": 1536, 623 | "text": "{ 624 | name: 'url', 625 | type: FloatType, 626 | resolve: ({ url }) => url, 627 | }", 628 | }, 629 | Object { 630 | "category": 1, 631 | "code": 2345, 632 | "length": 75, 633 | "messageText": Object { 634 | "category": 1, 635 | "code": 2345, 636 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; resolve: ({url}: Image) => string; }' is not assignable to parameter of type 'StrongFieldConfig'.", 637 | "next": Object { 638 | "category": 1, 639 | "code": 2326, 640 | "messageText": "Types of property 'type' are incompatible.", 641 | "next": Object { 642 | "category": 1, 643 | "code": 2322, 644 | "messageText": "Type 'StrongObjectType' is not assignable to type 'StrongOutputType | (() => StrongOutputType)'.", 645 | "next": Object { 646 | "category": 1, 647 | "code": 2322, 648 | "messageText": "Type 'StrongObjectType' is not assignable to type '() => StrongOutputType'.", 649 | "next": Object { 650 | "category": 1, 651 | "code": 2658, 652 | "messageText": "Type 'StrongObjectType' provides no match for the signature '(): StrongOutputType'", 653 | "next": undefined, 654 | }, 655 | }, 656 | }, 657 | }, 658 | }, 659 | "start": 1664, 660 | "text": "{ 661 | name: 'url', 662 | type: PersonType, 663 | resolve: ({ url }) => url, 664 | }", 665 | }, 666 | Object { 667 | "category": 1, 668 | "code": 2345, 669 | "length": 105, 670 | "messageText": Object { 671 | "category": 1, 672 | "code": 2345, 673 | "messageText": "Argument of type '{ name: string; type: StrongInputType; resolve: ({url}: Image) => string; }' is not assignable to parameter of type 'StrongFieldConfig'.", 674 | "next": Object { 675 | "category": 1, 676 | "code": 2326, 677 | "messageText": "Types of property 'type' are incompatible.", 678 | "next": Object { 679 | "category": 1, 680 | "code": 2322, 681 | "messageText": "Type 'StrongInputType' is not assignable to type 'StrongOutputType | (() => StrongOutputType)'.", 682 | "next": Object { 683 | "category": 1, 684 | "code": 2322, 685 | "messageText": "Type 'StrongInputType' is not assignable to type '() => StrongOutputType'.", 686 | "next": Object { 687 | "category": 1, 688 | "code": 2658, 689 | "messageText": "Type 'StrongInputType' provides no match for the signature '(): StrongOutputType'", 690 | "next": undefined, 691 | }, 692 | }, 693 | }, 694 | }, 695 | }, 696 | "start": 1819, 697 | "text": "{ 698 | name: 'url', 699 | type: (null as any) as StrongInputType, 700 | resolve: ({ url }) => url, 701 | }", 702 | }, 703 | Object { 704 | "category": 1, 705 | "code": 2345, 706 | "length": 88, 707 | "messageText": Object { 708 | "category": 1, 709 | "code": 2345, 710 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; resolve: ({size}: Image) => string; }' is not assignable to parameter of type 'StrongFieldConfig'.", 711 | "next": Object { 712 | "category": 1, 713 | "code": 2326, 714 | "messageText": "Types of property 'resolve' are incompatible.", 715 | "next": Object { 716 | "category": 1, 717 | "code": 2322, 718 | "messageText": "Type '({size}: Image) => string' is not assignable to type '(source: Image, args: {}, context: {}) => number | Promise | null | un...'.", 719 | "next": Object { 720 | "category": 1, 721 | "code": 2322, 722 | "messageText": "Type 'string' is not assignable to type 'number | Promise | null | undefined'.", 723 | "next": undefined, 724 | }, 725 | }, 726 | }, 727 | }, 728 | "start": 2177, 729 | "text": "{ 730 | name: 'size', 731 | type: FloatType, 732 | resolve: ({ size }) => 'not a number!', 733 | }", 734 | }, 735 | Object { 736 | "category": 1, 737 | "code": 2345, 738 | "length": 77, 739 | "messageText": Object { 740 | "category": 1, 741 | "code": 2345, 742 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; resolve: ({size}: Image) => number | undefin...' is not assignable to parameter of type 'StrongFieldConfig'.", 743 | "next": Object { 744 | "category": 1, 745 | "code": 2326, 746 | "messageText": "Types of property 'resolve' are incompatible.", 747 | "next": Object { 748 | "category": 1, 749 | "code": 2322, 750 | "messageText": "Type '({size}: Image) => number | undefined' is not assignable to type '(source: Image, args: {}, context: {}) => number | Promise'.", 751 | "next": Object { 752 | "category": 1, 753 | "code": 2322, 754 | "messageText": "Type 'number | undefined' is not assignable to type 'number | Promise'.", 755 | "next": Object { 756 | "category": 1, 757 | "code": 2322, 758 | "messageText": "Type 'undefined' is not assignable to type 'number | Promise'.", 759 | "next": undefined, 760 | }, 761 | }, 762 | }, 763 | }, 764 | }, 765 | "start": 2327, 766 | "text": "{ 767 | name: 'size', 768 | type: FloatType, 769 | resolve: ({ size }) => size, 770 | }", 771 | }, 772 | Object { 773 | "category": 1, 774 | "code": 2345, 775 | "length": 95, 776 | "messageText": Object { 777 | "category": 1, 778 | "code": 2345, 779 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; resolve: ({url}: Image) =...' is not assignable to parameter of type 'StrongFieldConfig'.", 780 | "next": Object { 781 | "category": 1, 782 | "code": 2326, 783 | "messageText": "Types of property 'type' are incompatible.", 784 | "next": Object { 785 | "category": 1, 786 | "code": 2322, 787 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongOutputType | (() => StrongOutputType)'.", 788 | "next": Object { 789 | "category": 1, 790 | "code": 2322, 791 | "messageText": "Type 'StrongInputOutputType' is not assignable to type '() => StrongOutputType'.", 792 | "next": Object { 793 | "category": 1, 794 | "code": 2658, 795 | "messageText": "Type 'StrongInputOutputType' provides no match for the signature '(): StrongOutputType'", 796 | "next": undefined, 797 | }, 798 | }, 799 | }, 800 | }, 801 | }, 802 | "start": 2683, 803 | "text": "{ 804 | name: 'url', 805 | type: createNullableType(StringType), 806 | resolve: ({ url }) => url, 807 | }", 808 | }, 809 | Object { 810 | "category": 1, 811 | "code": 2345, 812 | "length": 313, 813 | "messageText": Object { 814 | "category": 1, 815 | "code": 2345, 816 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; args: { width: { type: StrongInputOutputType...' is not assignable to parameter of type 'StrongFieldConfigWithArgs; args: { width: { type: StrongInputOutputType...' is not assignable to type 'StrongFieldConfig' is not assignable to type 'StrongOutputType | (() => StrongOutputType)'.", 829 | "next": Object { 830 | "category": 1, 831 | "code": 2322, 832 | "messageText": "Type 'StrongInputOutputType' is not assignable to type '() => StrongOutputType'.", 833 | "next": Object { 834 | "category": 1, 835 | "code": 2658, 836 | "messageText": "Type 'StrongInputOutputType' provides no match for the signature '(): StrongOutputType'", 837 | "next": undefined, 838 | }, 839 | }, 840 | }, 841 | }, 842 | }, 843 | }, 844 | "start": 3071, 845 | "text": "{ 846 | name: 'image', 847 | type: StringType, 848 | args: { 849 | width: { 850 | type: IntegerType, 851 | }, 852 | height: { 853 | type: createNullableType(IntegerType), 854 | }, 855 | }, 856 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 857 | url: imageURL, 858 | width, 859 | height, 860 | } : null, 861 | }", 862 | }, 863 | Object { 864 | "category": 1, 865 | "code": 2345, 866 | "length": 311, 867 | "messageText": Object { 868 | "category": 1, 869 | "code": 2345, 870 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { width: { type: StrongInputOutputType; args: { width: { type: StrongInputOutputType; }; height: { type: StrongInputOutputType | undefined'.", 883 | "next": Object { 884 | "category": 1, 885 | "code": 2322, 886 | "messageText": "Type '{ width: { type: StrongInputOutputType; }; height: { type: StrongInputOutputType'.", 887 | "next": Object { 888 | "category": 1, 889 | "code": 2326, 890 | "messageText": "Types of property 'width' are incompatible.", 891 | "next": Object { 892 | "category": 1, 893 | "code": 2322, 894 | "messageText": "Type '{ type: StrongInputOutputType; }' is not assignable to type 'StrongArgConfig'.", 895 | "next": Object { 896 | "category": 1, 897 | "code": 2326, 898 | "messageText": "Types of property 'type' are incompatible.", 899 | "next": Object { 900 | "category": 1, 901 | "code": 2322, 902 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 903 | "next": Object { 904 | "category": 1, 905 | "code": 2326, 906 | "messageText": "Types of property 'nullable' are incompatible.", 907 | "next": Object { 908 | "category": 1, 909 | "code": 2322, 910 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongInputType'.", 911 | "next": Object { 912 | "category": 1, 913 | "code": 2322, 914 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 915 | "next": Object { 916 | "category": 1, 917 | "code": 2326, 918 | "messageText": "Types of property 'nullable' are incompatible.", 919 | "next": Object { 920 | "category": 1, 921 | "code": 2322, 922 | "messageText": "Type '() => StrongInputOutputType' is not assignable to type '() => StrongInputType'.", 923 | "next": Object { 924 | "category": 1, 925 | "code": 2322, 926 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 927 | "next": undefined, 928 | }, 929 | }, 930 | }, 931 | }, 932 | }, 933 | }, 934 | }, 935 | }, 936 | }, 937 | }, 938 | }, 939 | }, 940 | }, 941 | }, 942 | }, 943 | "start": 3495, 944 | "text": "{ 945 | name: 'image', 946 | type: ImageType, 947 | args: { 948 | width: { 949 | type: StringType, 950 | }, 951 | height: { 952 | type: createNullableType(IntegerType), 953 | }, 954 | }, 955 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 956 | url: imageURL, 957 | width, 958 | height, 959 | } : null, 960 | }", 961 | }, 962 | Object { 963 | "category": 1, 964 | "code": 2345, 965 | "length": 311, 966 | "messageText": Object { 967 | "category": 1, 968 | "code": 2345, 969 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { width: { type: StrongInputOutputType; args: { width: { type: StrongInputOutputType; }; height: { type: StrongInputOutputType | undefined'.", 982 | "next": Object { 983 | "category": 1, 984 | "code": 2322, 985 | "messageText": "Type '{ width: { type: StrongInputOutputType; }; height: { type: StrongInputOutputType'.", 986 | "next": Object { 987 | "category": 1, 988 | "code": 2326, 989 | "messageText": "Types of property 'height' are incompatible.", 990 | "next": Object { 991 | "category": 1, 992 | "code": 2322, 993 | "messageText": "Type '{ type: StrongInputOutputType; }' is not assignable to type 'StrongArgConfig'.", 994 | "next": Object { 995 | "category": 1, 996 | "code": 2326, 997 | "messageText": "Types of property 'type' are incompatible.", 998 | "next": Object { 999 | "category": 1, 1000 | "code": 2322, 1001 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 1002 | "next": undefined, 1003 | }, 1004 | }, 1005 | }, 1006 | }, 1007 | }, 1008 | }, 1009 | }, 1010 | }, 1011 | }, 1012 | "start": 3917, 1013 | "text": "{ 1014 | name: 'image', 1015 | type: ImageType, 1016 | args: { 1017 | width: { 1018 | type: IntegerType, 1019 | }, 1020 | height: { 1021 | type: createNullableType(StringType), 1022 | }, 1023 | }, 1024 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1025 | url: imageURL, 1026 | width, 1027 | height, 1028 | } : null, 1029 | }", 1030 | }, 1031 | Object { 1032 | "category": 1, 1033 | "code": 2345, 1034 | "length": 170, 1035 | "messageText": Object { 1036 | "category": 1, 1037 | "code": 2345, 1038 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; resolve: ({imageURL}: Person, {width, height}:...' is not assignable to parameter of type 'StrongFieldConfigWithArgs; resolve: ({imageURL}: Person, {width, height}:...' is not assignable to type '{ readonly args: StrongArgsConfig<{ width: number; height: number | null | undefined; }>; }'.", 1043 | "next": Object { 1044 | "category": 1, 1045 | "code": 2324, 1046 | "messageText": "Property 'args' is missing in type '{ name: string; type: StrongObjectType; resolve: ({imageURL}: Person, {width, height}:...'.", 1047 | "next": undefined, 1048 | }, 1049 | }, 1050 | }, 1051 | "start": 4345, 1052 | "text": "{ 1053 | name: 'image', 1054 | type: ImageType, 1055 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1056 | url: imageURL, 1057 | width, 1058 | height, 1059 | } : null, 1060 | }", 1061 | }, 1062 | Object { 1063 | "category": 1, 1064 | "code": 2345, 1065 | "length": 261, 1066 | "messageText": Object { 1067 | "category": 1, 1068 | "code": 2345, 1069 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { height: { type: StrongInputOutputType<...' is not assignable to parameter of type 'StrongFieldConfigWithArgs; args: { height: { type: StrongInputOutputType<...' is not assignable to type 'StrongFieldConfig; }; }' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }> | undefined'.", 1082 | "next": Object { 1083 | "category": 1, 1084 | "code": 2322, 1085 | "messageText": "Type '{ height: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }>'.", 1086 | "next": Object { 1087 | "category": 1, 1088 | "code": 2324, 1089 | "messageText": "Property 'width' is missing in type '{ height: { type: StrongInputOutputType; }; }'.", 1090 | "next": undefined, 1091 | }, 1092 | }, 1093 | }, 1094 | }, 1095 | }, 1096 | }, 1097 | "start": 4629, 1098 | "text": "{ 1099 | name: 'image', 1100 | type: ImageType, 1101 | args: { 1102 | height: { 1103 | type: createNullableType(IntegerType), 1104 | }, 1105 | }, 1106 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1107 | url: imageURL, 1108 | width, 1109 | height, 1110 | } : null, 1111 | }", 1112 | }, 1113 | Object { 1114 | "category": 1, 1115 | "code": 2345, 1116 | "length": 240, 1117 | "messageText": Object { 1118 | "category": 1, 1119 | "code": 2345, 1120 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { width: { type: StrongInputOutputType; args: { width: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }> | undefined'.", 1133 | "next": Object { 1134 | "category": 1, 1135 | "code": 2322, 1136 | "messageText": "Type '{ width: { type: StrongInputOutputType; }; }' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }>'.", 1137 | "next": Object { 1138 | "category": 1, 1139 | "code": 2324, 1140 | "messageText": "Property 'height' is missing in type '{ width: { type: StrongInputOutputType; }; }'.", 1141 | "next": undefined, 1142 | }, 1143 | }, 1144 | }, 1145 | }, 1146 | }, 1147 | }, 1148 | "start": 5004, 1149 | "text": "{ 1150 | name: 'image', 1151 | type: ImageType, 1152 | args: { 1153 | width: { 1154 | type: IntegerType, 1155 | }, 1156 | }, 1157 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1158 | url: imageURL, 1159 | width, 1160 | height, 1161 | } : null, 1162 | }", 1163 | }, 1164 | Object { 1165 | "category": 1, 1166 | "code": 2345, 1167 | "length": 332, 1168 | "messageText": Object { 1169 | "category": 1, 1170 | "code": 2345, 1171 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { width: { type: StrongInputOutputType; args: { width: { type: StrongInputOutputType; }; height: { type: StrongInput...' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }> | undefined'.", 1184 | "next": Object { 1185 | "category": 1, 1186 | "code": 2322, 1187 | "messageText": "Type '{ width: { type: StrongInputOutputType; }; height: { type: StrongInput...' is not assignable to type 'StrongArgsConfig<{ width: number; height: number | null | undefined; }>'.", 1188 | "next": Object { 1189 | "category": 1, 1190 | "code": 2326, 1191 | "messageText": "Types of property 'width' are incompatible.", 1192 | "next": Object { 1193 | "category": 1, 1194 | "code": 2322, 1195 | "messageText": "Type '{ type: StrongInputOutputType; }' is not assignable to type 'StrongArgConfig'.", 1196 | "next": Object { 1197 | "category": 1, 1198 | "code": 2326, 1199 | "messageText": "Types of property 'type' are incompatible.", 1200 | "next": Object { 1201 | "category": 1, 1202 | "code": 2322, 1203 | "messageText": "Type 'StrongInputOutputType' is not assignable to type 'StrongInputType'.", 1204 | "next": Object { 1205 | "category": 1, 1206 | "code": 2326, 1207 | "messageText": "Types of property '_strongValue' are incompatible.", 1208 | "next": Object { 1209 | "category": 1, 1210 | "code": 2322, 1211 | "messageText": "Type 'number | null | undefined' is not assignable to type 'number'.", 1212 | "next": Object { 1213 | "category": 1, 1214 | "code": 2322, 1215 | "messageText": "Type 'undefined' is not assignable to type 'number'.", 1216 | "next": undefined, 1217 | }, 1218 | }, 1219 | }, 1220 | }, 1221 | }, 1222 | }, 1223 | }, 1224 | }, 1225 | }, 1226 | }, 1227 | }, 1228 | }, 1229 | "start": 5371, 1230 | "text": "{ 1231 | name: 'image', 1232 | type: ImageType, 1233 | args: { 1234 | width: { 1235 | type: createNullableType(IntegerType), 1236 | }, 1237 | height: { 1238 | type: createNullableType(IntegerType), 1239 | }, 1240 | }, 1241 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1242 | url: imageURL, 1243 | width, 1244 | height, 1245 | } : null, 1246 | }", 1247 | }, 1248 | Object { 1249 | "category": 1, 1250 | "code": 2459, 1251 | "length": 5, 1252 | "messageText": "Type '{}' has no property 'width' and no string index signature.", 1253 | "start": 5977, 1254 | "text": "width", 1255 | }, 1256 | Object { 1257 | "category": 1, 1258 | "code": 2459, 1259 | "length": 6, 1260 | "messageText": "Type '{}' has no property 'height' and no string index signature.", 1261 | "start": 5984, 1262 | "text": "height", 1263 | }, 1264 | Object { 1265 | "category": 1, 1266 | "code": 2459, 1267 | "length": 1, 1268 | "messageText": "Type '{ width: number; height: number | null | undefined; }' has no property 'x' and no string index signature.", 1269 | "start": 7049, 1270 | "text": "x", 1271 | }, 1272 | Object { 1273 | "category": 1, 1274 | "code": 2459, 1275 | "length": 1, 1276 | "messageText": "Type '{ width: number; height: number | null | undefined; }' has no property 'y' and no string index signature.", 1277 | "start": 7052, 1278 | "text": "y", 1279 | }, 1280 | Object { 1281 | "category": 1, 1282 | "code": 2459, 1283 | "length": 1, 1284 | "messageText": "Type '{ width: number; height: number | null | undefined; }' has no property 'z' and no string index signature.", 1285 | "start": 7055, 1286 | "text": "z", 1287 | }, 1288 | Object { 1289 | "category": 1, 1290 | "code": 2345, 1291 | "length": 342, 1292 | "messageText": Object { 1293 | "category": 1, 1294 | "code": 2345, 1295 | "messageText": "Argument of type '{ name: string; type: StrongObjectType; args: { width: { type: StrongOutputType; args: { width: { type: StrongOutputType; }; height: { type: StrongInputOutputType | undefined'.", 1308 | "next": Object { 1309 | "category": 1, 1310 | "code": 2322, 1311 | "messageText": "Type '{ width: { type: StrongOutputType; }; height: { type: StrongInputOutputType'.", 1312 | "next": Object { 1313 | "category": 1, 1314 | "code": 2326, 1315 | "messageText": "Types of property 'width' are incompatible.", 1316 | "next": Object { 1317 | "category": 1, 1318 | "code": 2322, 1319 | "messageText": "Type '{ type: StrongOutputType; }' is not assignable to type 'StrongArgConfig'.", 1320 | "next": Object { 1321 | "category": 1, 1322 | "code": 2326, 1323 | "messageText": "Types of property 'type' are incompatible.", 1324 | "next": Object { 1325 | "category": 1, 1326 | "code": 2322, 1327 | "messageText": "Type 'StrongOutputType' is not assignable to type 'StrongInputType'.", 1328 | "next": Object { 1329 | "category": 1, 1330 | "code": 2324, 1331 | "messageText": "Property '_strongInputType' is missing in type 'StrongOutputType'.", 1332 | "next": undefined, 1333 | }, 1334 | }, 1335 | }, 1336 | }, 1337 | }, 1338 | }, 1339 | }, 1340 | }, 1341 | }, 1342 | }, 1343 | "start": 7712, 1344 | "text": "{ 1345 | name: 'image', 1346 | type: ImageType, 1347 | args: { 1348 | width: { 1349 | type: (null as any) as StrongOutputType, 1350 | }, 1351 | height: { 1352 | type: createNullableType(IntegerType), 1353 | }, 1354 | }, 1355 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 1356 | url: imageURL, 1357 | width, 1358 | height, 1359 | } : null, 1360 | }", 1361 | }, 1362 | Object { 1363 | "category": 1, 1364 | "code": 2339, 1365 | "length": 12, 1366 | "messageText": "Property 'ImageService' does not exist on type '{}'.", 1367 | "start": 8937, 1368 | "text": "ImageService", 1369 | }, 1370 | Object { 1371 | "category": 1, 1372 | "code": 2448, 1373 | "length": 4, 1374 | "messageText": "Block-scoped variable 'type' used before its declaration.", 1375 | "start": 9170, 1376 | "text": "type", 1377 | }, 1378 | Object { 1379 | "category": 1, 1380 | "code": 2454, 1381 | "length": 4, 1382 | "messageText": "Variable 'type' is used before being assigned.", 1383 | "start": 9170, 1384 | "text": "type", 1385 | }, 1386 | Object { 1387 | "category": 1, 1388 | "code": 2345, 1389 | "length": 97, 1390 | "messageText": Object { 1391 | "category": 1, 1392 | "code": 2345, 1393 | "messageText": "Argument of type '{ name: string; type: StrongInputOutputType; resolve: () => Promise; }' is not assignable to parameter of type 'StrongFieldConfig'.", 1394 | "next": Object { 1395 | "category": 1, 1396 | "code": 2326, 1397 | "messageText": "Types of property 'resolve' are incompatible.", 1398 | "next": Object { 1399 | "category": 1, 1400 | "code": 2322, 1401 | "messageText": "Type '() => Promise' is not assignable to type '(source: Image, args: {}, context: {}) => number | Promise | null | un...'.", 1402 | "next": Object { 1403 | "category": 1, 1404 | "code": 2322, 1405 | "messageText": "Type 'Promise' is not assignable to type 'number | Promise | null | undefined'.", 1406 | "next": Object { 1407 | "category": 1, 1408 | "code": 2322, 1409 | "messageText": "Type 'Promise' is not assignable to type 'Promise'.", 1410 | "next": Object { 1411 | "category": 1, 1412 | "code": 2322, 1413 | "messageText": "Type 'string' is not assignable to type 'number | null | undefined'.", 1414 | "next": undefined, 1415 | }, 1416 | }, 1417 | }, 1418 | }, 1419 | }, 1420 | }, 1421 | "start": 9684, 1422 | "text": "{ 1423 | name: 'size', 1424 | type: FloatType, 1425 | resolve: () => Promise.resolve('not a number!'), 1426 | }", 1427 | }, 1428 | ] 1429 | `; 1430 | 1431 | exports[`scalar-fixture.ts 1`] = ` 1432 | Array [ 1433 | Object { 1434 | "category": 1, 1435 | "code": 2345, 1436 | "length": 107, 1437 | "messageText": Object { 1438 | "category": 1, 1439 | "code": 2345, 1440 | "messageText": "Argument of type '{ name: string; serialize: (value: string) => number; parseValue: (value: number) => string; }' is not assignable to parameter of type 'StrongScalarTypeConfigWithInput'.", 1441 | "next": Object { 1442 | "category": 1, 1443 | "code": 2324, 1444 | "messageText": "Property 'parseLiteral' is missing in type '{ name: string; serialize: (value: string) => number; parseValue: (value: number) => string; }'.", 1445 | "next": undefined, 1446 | }, 1447 | }, 1448 | "start": 339, 1449 | "text": "{ 1450 | name: 'foo', 1451 | serialize: value => parseInt(value, 10), 1452 | parseValue: value => String(value), 1453 | }", 1454 | }, 1455 | Object { 1456 | "category": 1, 1457 | "code": 2345, 1458 | "length": 106, 1459 | "messageText": Object { 1460 | "category": 1, 1461 | "code": 2345, 1462 | "messageText": "Argument of type '{ name: string; serialize: (value: string) => number; parseLiteral: (value: ValueNode) => \\"Variab...' is not assignable to parameter of type 'StrongScalarTypeConfigWithInput'.", 1463 | "next": Object { 1464 | "category": 1, 1465 | "code": 2324, 1466 | "messageText": "Property 'parseValue' is missing in type '{ name: string; serialize: (value: string) => number; parseLiteral: (value: ValueNode) => \\"Variab...'.", 1467 | "next": undefined, 1468 | }, 1469 | }, 1470 | "start": 553, 1471 | "text": "{ 1472 | name: 'foo', 1473 | serialize: value => parseInt(value, 10), 1474 | parseLiteral: value => value.kind, 1475 | }", 1476 | }, 1477 | Object { 1478 | "category": 1, 1479 | "code": 2322, 1480 | "length": 5, 1481 | "messageText": Object { 1482 | "category": 1, 1483 | "code": 2322, 1484 | "messageText": "Type 'StrongOutputType' is not assignable to type 'StrongInputType'.", 1485 | "next": Object { 1486 | "category": 1, 1487 | "code": 2324, 1488 | "messageText": "Property '_strongInputType' is missing in type 'StrongOutputType'.", 1489 | "next": undefined, 1490 | }, 1491 | }, 1492 | "start": 869, 1493 | "text": "type2", 1494 | }, 1495 | ] 1496 | `; 1497 | 1498 | exports[`schema-fixture.ts 1`] = ` 1499 | Array [ 1500 | Object { 1501 | "category": 1, 1502 | "code": 2345, 1503 | "length": 8, 1504 | "messageText": "Argument of type '\\"string\\"' is not assignable to parameter of type 'number'.", 1505 | "start": 368, 1506 | "text": "'string'", 1507 | }, 1508 | Object { 1509 | "category": 1, 1510 | "code": 2345, 1511 | "length": 8, 1512 | "messageText": "Argument of type '\\"string\\"' is not assignable to parameter of type 'number'.", 1513 | "start": 530, 1514 | "text": "'string'", 1515 | }, 1516 | Object { 1517 | "category": 1, 1518 | "code": 2345, 1519 | "length": 16, 1520 | "messageText": Object { 1521 | "category": 1, 1522 | "code": 2345, 1523 | "messageText": "Argument of type '{ query: StrongObjectType; }' is not assignable to parameter of type 'StrongSchemaConfig'.", 1524 | "next": Object { 1525 | "category": 1, 1526 | "code": 2326, 1527 | "messageText": "Types of property 'query' are incompatible.", 1528 | "next": Object { 1529 | "category": 1, 1530 | "code": 2322, 1531 | "messageText": "Type 'StrongObjectType' is not assignable to type 'StrongObjectType'.", 1532 | "next": Object { 1533 | "category": 1, 1534 | "code": 2322, 1535 | "messageText": "Type 'string' is not assignable to type 'number'.", 1536 | "next": undefined, 1537 | }, 1538 | }, 1539 | }, 1540 | }, 1541 | "start": 875, 1542 | "text": "{ 1543 | query, 1544 | }", 1545 | }, 1546 | Object { 1547 | "category": 1, 1548 | "code": 2345, 1549 | "length": 16, 1550 | "messageText": Object { 1551 | "category": 1, 1552 | "code": 2345, 1553 | "messageText": "Argument of type '{ query: StrongObjectType; }' is not assignable to parameter of type 'StrongSchemaConfig'.", 1554 | "next": Object { 1555 | "category": 1, 1556 | "code": 2326, 1557 | "messageText": "Types of property 'query' are incompatible.", 1558 | "next": Object { 1559 | "category": 1, 1560 | "code": 2322, 1561 | "messageText": "Type 'StrongObjectType' is not assignable to type 'StrongObjectType'.", 1562 | "next": Object { 1563 | "category": 1, 1564 | "code": 2322, 1565 | "messageText": "Type 'string' is not assignable to type 'number'.", 1566 | "next": undefined, 1567 | }, 1568 | }, 1569 | }, 1570 | }, 1571 | "start": 1046, 1572 | "text": "{ 1573 | query, 1574 | }", 1575 | }, 1576 | ] 1577 | `; 1578 | 1579 | exports[`wrap-fixture.ts 1`] = ` 1580 | Array [ 1581 | Object { 1582 | "category": 1, 1583 | "code": 2322, 1584 | "length": 6, 1585 | "messageText": Object { 1586 | "category": 1, 1587 | "code": 2322, 1588 | "messageText": "Type 'StrongInputType<{}>' is not assignable to type 'StrongOutputType<{}>'.", 1589 | "next": Object { 1590 | "category": 1, 1591 | "code": 2324, 1592 | "messageText": "Property '_strongOutputType' is missing in type 'StrongInputType<{}>'.", 1593 | "next": undefined, 1594 | }, 1595 | }, 1596 | "start": 391, 1597 | "text": "output", 1598 | }, 1599 | Object { 1600 | "category": 1, 1601 | "code": 2322, 1602 | "length": 6, 1603 | "messageText": "Type 'StrongInputType<{}>' is not assignable to type 'StrongOutputType<{}>'.", 1604 | "start": 597, 1605 | "text": "output", 1606 | }, 1607 | Object { 1608 | "category": 1, 1609 | "code": 2322, 1610 | "length": 5, 1611 | "messageText": Object { 1612 | "category": 1, 1613 | "code": 2322, 1614 | "messageText": "Type 'StrongOutputType<{}>' is not assignable to type 'StrongInputType<{}>'.", 1615 | "next": Object { 1616 | "category": 1, 1617 | "code": 2324, 1618 | "messageText": "Property '_strongInputType' is missing in type 'StrongOutputType<{}>'.", 1619 | "next": undefined, 1620 | }, 1621 | }, 1622 | "start": 755, 1623 | "text": "input", 1624 | }, 1625 | Object { 1626 | "category": 1, 1627 | "code": 2322, 1628 | "length": 5, 1629 | "messageText": "Type 'StrongOutputType<{}>' is not assignable to type 'StrongInputType<{}>'.", 1630 | "start": 956, 1631 | "text": "input", 1632 | }, 1633 | ] 1634 | `; 1635 | -------------------------------------------------------------------------------- /src/__tests__/description-test.ts: -------------------------------------------------------------------------------- 1 | import { trimDescription, trimDescriptionsInConfig } from '../description'; 2 | 3 | test('trimDescription will correctly format a multiline string into a description', () => { 4 | expect(trimDescription(` 5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquam dapibus 6 | diam eu convallis. Nullam tincidunt mi quis purus congue, eu commodo eros 7 | dapibus. Vivamus maximus ex nisi, sit amet varius dui vestibulum et. Duis in 8 | sem eros. Suspendisse libero ipsum, vulputate sed tortor placerat, convallis 9 | porta tortor. Etiam fringilla iaculis venenatis. Donec vitae mauris quis dui 10 | convallis ullamcorper. Nulla a velit neque. Suspendisse ac tincidunt lacus. 11 | Vivamus sit amet rhoncus dolor, id laoreet neque. 12 | 13 | Duis vulputate, dolor in dictum faucibus, velit nibh laoreet sem, a vehicula 14 | lorem felis a eros. Nulla tortor dolor, imperdiet vel enim eget, gravida 15 | dapibus ante. Cras fringilla erat at tortor cursus auctor. Donec elementum 16 | lacus malesuada, lobortis tellus eleifend, posuere velit. Sed neque sem, 17 | pharetra quis tellus eget, ornare hendrerit velit. Vivamus in urna eu libero 18 | vulputate dapibus. Nunc rhoncus luctus lacus, ac molestie augue finibus ut. 19 | Phasellus lobortis fermentum justo, id consectetur nisi ultrices nec. 20 | 21 | 1. Nunc quis lacus semper, vehicula magna nec, aliquet magna. 22 | 2. Suspendisse consequat nunc in vehicula elementum. 23 | 3. Aenean et metus in enim ultricies elementum. 24 | 4. Suspendisse laoreet turpis eu nisl tempus vestibulum. 25 | 26 | Pellentesque viverra efficitur magna ut malesuada. Nunc aliquet luctus 27 | convallis. Sed ac nulla arcu. Duis felis dui, placerat et enim quis, 28 | elementum dapibus quam. Mauris sem nulla, suscipit ut dolor a, tempus varius 29 | ligula. Ut hendrerit ultrices orci, vel rutrum purus convallis tincidunt. 30 | Sed varius arcu ullamcorper, ultrices turpis non, tempus est. Morbi et 31 | tortor ac risus lacinia malesuada sit amet ac nisl. Nulla fringilla elit nec 32 | tellus euismod, eu cursus ipsum ultricies. Etiam fermentum vestibulum felis, 33 | ac feugiat velit facilisis at. 34 | 35 | - Cras dapibus sem id vulputate commodo. 36 | - Ut viverra ipsum eget sem dignissim porttitor. 37 | `)).toMatchSnapshot(); 38 | }); 39 | 40 | test('trimDescriptionsInConfig will trim descriptions in nested objects', () => { 41 | class NotPlainObject { 42 | constructor(object) { 43 | Object.assign(this, object); 44 | } 45 | } 46 | 47 | expect(trimDescriptionsInConfig({ 48 | a: 1, 49 | b: 2, 50 | description: ` Hello, world! `, 51 | c: { 52 | d: { 53 | description: ` 54 | Have you ever been down the water spout? 55 | To the very bottom of the water system? 56 | There you will find a little aligator 57 | He goes by the name of Alfred if you do he’s mine. 58 | I lost him. 59 | I threw him down the water spout 60 | and now I’m very lonely ‘cuz he’s gone. 61 | I miss him. 62 | `, 63 | e: 3, 64 | f: 4, 65 | g: { 66 | h: 5, 67 | i: 6, 68 | }, 69 | j: new NotPlainObject({ 70 | k: 7, 71 | l: 8, 72 | description: ` Space allowed!!! `, 73 | }), 74 | }, 75 | }, 76 | })).toMatchSnapshot(); 77 | }); 78 | -------------------------------------------------------------------------------- /src/__tests__/fixtures-test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import * as ts from 'typescript'; 4 | 5 | const compilerOptions: ts.CompilerOptions = { 6 | target: ts.ScriptTarget.ES2015, 7 | noEmit: true, 8 | noImplicitAny: true, 9 | strictNullChecks: true, 10 | include: [path.resolve(__dirname, '../../src')], 11 | }; 12 | 13 | const fixturesDir = path.resolve(__dirname, 'fixtures'); 14 | const fixtureNames = fs.readdirSync(fixturesDir); 15 | const fixtureFileNames = fixtureNames.map(name => path.resolve(fixturesDir, name)); 16 | const fixtureFiles = fixtureFileNames.map(fileName => fs.readFileSync(fileName, 'utf8')); 17 | const program = ts.createProgram(fixtureFileNames, compilerOptions); 18 | const emitResult = program.emit(); 19 | const allDiagnostics = [...ts.getPreEmitDiagnostics(program), ...emitResult.diagnostics]; 20 | 21 | fixtureNames.forEach((name, i) => { 22 | test(name, () => { 23 | const diagnostics = allDiagnostics 24 | .filter(({ file }) => file.fileName === fixtureFileNames[i]) 25 | .map(({ file, ...diagnostic }) => ({ 26 | ...diagnostic, 27 | text: file.text.slice(diagnostic.start, diagnostic.start + diagnostic.length), 28 | })); 29 | expect(diagnostics).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/interface-fixture.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StringType, 3 | IntegerType, 4 | createInterfaceType, 5 | createObjectType, 6 | } from '../../index'; 7 | 8 | interface Person { 9 | name: string; 10 | email: string; 11 | } 12 | 13 | // All good 14 | { 15 | const type = createInterfaceType<{ 16 | foo: { type: string }, 17 | bar: { type: string | null | undefined }, 18 | }>({ 19 | name: 'Foobar', 20 | resolveType: undefined as any, 21 | fields: { 22 | foo: { type: StringType }, 23 | bar: { type: StringType.nullable() }, 24 | }, 25 | }); 26 | } 27 | 28 | // Bad field type 1 29 | { 30 | const type = createInterfaceType<{ 31 | foo: { type: string }, 32 | bar: { type: string | null | undefined }, 33 | }>({ 34 | name: 'Foobar', 35 | resolveType: undefined as any, 36 | fields: { 37 | foo: { type: IntegerType }, 38 | bar: { type: StringType.nullable() }, 39 | }, 40 | }); 41 | } 42 | 43 | // Bad field type 2 44 | { 45 | const type = createInterfaceType<{ 46 | foo: { type: string }, 47 | bar: { type: string | null | undefined }, 48 | }>({ 49 | name: 'Foobar', 50 | resolveType: undefined as any, 51 | fields: { 52 | foo: { type: StringType }, 53 | bar: { type: IntegerType.nullable() }, 54 | }, 55 | }); 56 | } 57 | 58 | // Missing field 1 59 | { 60 | const type = createInterfaceType<{ 61 | foo: { type: string }, 62 | bar: { type: string | null | undefined }, 63 | }>({ 64 | name: 'Foobar', 65 | resolveType: undefined as any, 66 | fields: { 67 | foo: { type: StringType }, 68 | }, 69 | }); 70 | } 71 | 72 | // Missing field 2 73 | { 74 | const type = createInterfaceType<{ 75 | foo: { type: string }, 76 | bar: { type: string | null | undefined }, 77 | }>({ 78 | name: 'Foobar', 79 | resolveType: undefined as any, 80 | fields: { 81 | bar: { type: StringType.nullable() }, 82 | }, 83 | }); 84 | } 85 | 86 | // All good with args 87 | { 88 | const type = createInterfaceType<{ 89 | x: { 90 | type: string, 91 | args: { 92 | foo: string, 93 | bar: string | null | undefined, 94 | }, 95 | }, 96 | }>({ 97 | name: 'Foobar', 98 | resolveType: undefined as any, 99 | fields: { 100 | x: { 101 | type: StringType, 102 | args: { 103 | foo: { type: StringType }, 104 | bar: { type: StringType.nullable() }, 105 | }, 106 | }, 107 | }, 108 | }); 109 | } 110 | 111 | // Bad arg type 1 112 | { 113 | const type = createInterfaceType<{ 114 | x: { 115 | type: string, 116 | args: { 117 | foo: string, 118 | bar: string | null | undefined, 119 | }, 120 | }, 121 | }>({ 122 | name: 'Foobar', 123 | resolveType: undefined as any, 124 | fields: { 125 | x: { 126 | type: StringType, 127 | args: { 128 | foo: { type: IntegerType }, 129 | bar: { type: StringType.nullable() }, 130 | }, 131 | }, 132 | }, 133 | }); 134 | } 135 | 136 | // Bad arg type 2 137 | { 138 | const type = createInterfaceType<{ 139 | x: { 140 | type: string, 141 | args: { 142 | foo: string, 143 | bar: string | null | undefined, 144 | }, 145 | }, 146 | }>({ 147 | name: 'Foobar', 148 | resolveType: undefined as any, 149 | fields: { 150 | x: { 151 | type: StringType, 152 | args: { 153 | foo: { type: StringType }, 154 | bar: { type: IntegerType.nullable() }, 155 | }, 156 | }, 157 | }, 158 | }); 159 | } 160 | 161 | // Missing arg 1 162 | { 163 | const type = createInterfaceType<{ 164 | x: { 165 | type: string, 166 | args: { 167 | foo: string, 168 | bar: string | null | undefined, 169 | }, 170 | }, 171 | }>({ 172 | name: 'Foobar', 173 | resolveType: undefined as any, 174 | fields: { 175 | x: { 176 | type: StringType, 177 | args: { 178 | foo: { type: StringType }, 179 | }, 180 | }, 181 | }, 182 | }); 183 | } 184 | 185 | // Missing arg 2 186 | { 187 | const type = createInterfaceType<{ 188 | x: { 189 | type: string, 190 | args: { 191 | foo: string, 192 | bar: string | null | undefined, 193 | }, 194 | }, 195 | }>({ 196 | name: 'Foobar', 197 | resolveType: undefined as any, 198 | fields: { 199 | x: { 200 | type: StringType, 201 | args: { 202 | bar: { type: StringType.nullable() }, 203 | }, 204 | }, 205 | }, 206 | }); 207 | } 208 | 209 | // All good implementation 210 | { 211 | const ActorType = createInterfaceType<{ 212 | name: { type: string }, 213 | }>({ 214 | name: 'Actor', 215 | resolveType: undefined as any, 216 | fields: { 217 | name: { 218 | type: StringType, 219 | }, 220 | }, 221 | }); 222 | 223 | const PersonType = createObjectType({ 224 | name: 'Person', 225 | }) 226 | .implement(ActorType, { 227 | name: ({ name }) => name, 228 | }) 229 | .fieldNonNull({ 230 | name: 'email', 231 | type: StringType, 232 | resolve: ({ email }) => email, 233 | }); 234 | } 235 | 236 | // Missing field implementation 237 | { 238 | const ActorType = createInterfaceType<{ 239 | name: { type: string }, 240 | }>({ 241 | name: 'Actor', 242 | resolveType: undefined as any, 243 | fields: { 244 | name: { 245 | type: StringType, 246 | }, 247 | }, 248 | }); 249 | 250 | const PersonType = createObjectType({ 251 | name: 'Person', 252 | }) 253 | .implement(ActorType, { 254 | 255 | }) 256 | .fieldNonNull({ 257 | name: 'email', 258 | type: StringType, 259 | resolve: ({ email }) => email, 260 | }); 261 | } 262 | 263 | // Bad return type 264 | { 265 | const ActorType = createInterfaceType<{ 266 | name: { type: string }, 267 | }>({ 268 | name: 'Actor', 269 | resolveType: undefined as any, 270 | fields: { 271 | name: { 272 | type: StringType, 273 | }, 274 | }, 275 | }); 276 | 277 | const PersonType = createObjectType({ 278 | name: 'Person', 279 | }) 280 | .implement(ActorType, { 281 | name: () => 42, 282 | }) 283 | .fieldNonNull({ 284 | name: 'email', 285 | type: StringType, 286 | resolve: ({ email }) => email, 287 | }); 288 | } 289 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/object-fixture.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StrongInputType, 3 | StrongOutputType, 4 | StrongInputOutputType, 5 | StringType, 6 | FloatType, 7 | IntegerType, 8 | createNullableType, 9 | createObjectType, 10 | } from '../../index'; 11 | 12 | interface Person { 13 | name: string; 14 | email: string; 15 | imageURL?: string; 16 | } 17 | 18 | interface Image { 19 | url: string; 20 | size?: number; 21 | } 22 | 23 | interface Context { 24 | ImageService: { 25 | cropImage: (url: string, size: number) => Image, 26 | }; 27 | } 28 | 29 | const PersonType = createObjectType({ 30 | name: 'Person', 31 | }); 32 | 33 | const PersonTypeWithContext = createObjectType({ 34 | name: 'Person', 35 | }); 36 | 37 | const ImageType = createObjectType({ 38 | name: 'Image', 39 | }); 40 | 41 | // All good image 42 | ImageType 43 | .fieldNonNull({ 44 | name: 'url', 45 | type: StringType, 46 | resolve: ({ url }) => url, 47 | }) 48 | .field({ 49 | name: 'size', 50 | type: FloatType, 51 | resolve: ({ size }) => size, 52 | }); 53 | 54 | // All good person 55 | PersonType 56 | .fieldNonNull({ 57 | name: 'name', 58 | type: StringType, 59 | resolve: ({ name }) => name, 60 | }) 61 | .fieldNonNull({ 62 | name: 'email', 63 | type: StringType, 64 | resolve: ({ email }) => email, 65 | }) 66 | .field({ 67 | name: 'image', 68 | type: ImageType, 69 | args: { 70 | width: { 71 | type: IntegerType, 72 | }, 73 | height: { 74 | type: createNullableType(IntegerType), 75 | }, 76 | }, 77 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 78 | url: imageURL, 79 | width, 80 | height, 81 | } : null, 82 | }); 83 | 84 | // Wrong type 1 85 | ImageType 86 | .fieldNonNull({ 87 | name: 'url', 88 | type: FloatType, 89 | resolve: ({ url }) => url, 90 | }); 91 | 92 | // Wrong type 2 93 | ImageType 94 | .fieldNonNull({ 95 | name: 'url', 96 | type: PersonType, 97 | resolve: ({ url }) => url, 98 | }); 99 | 100 | // No input types in output type position 101 | ImageType 102 | .fieldNonNull({ 103 | name: 'url', 104 | type: (null as any) as StrongInputType, 105 | resolve: ({ url }) => url, 106 | }); 107 | 108 | // Input-output types in output type position ok 109 | ImageType 110 | .fieldNonNull({ 111 | name: 'url', 112 | type: (null as any) as StrongInputOutputType, 113 | resolve: ({ url }) => url, 114 | }); 115 | 116 | // Bad resolve return 1 117 | ImageType 118 | .field({ 119 | name: 'size', 120 | type: FloatType, 121 | resolve: ({ size }) => 'not a number!', 122 | }); 123 | 124 | // Bad resolve return 2 125 | ImageType 126 | .fieldNonNull({ 127 | name: 'size', 128 | type: FloatType, 129 | resolve: ({ size }) => size, 130 | }); 131 | 132 | // Non-null field configured to be createNullableType 133 | ImageType 134 | .fieldNonNull({ 135 | name: 'url', 136 | type: createNullableType(StringType), 137 | resolve: ({ url }) => url, 138 | }); 139 | 140 | // Nullable type in non-null field 141 | ImageType 142 | .fieldNonNull({ 143 | name: 'url', 144 | type: createNullableType(StringType), 145 | resolve: ({ url }) => url, 146 | }); 147 | 148 | // Nullable type in createNullableType field 149 | ImageType 150 | .field({ 151 | name: 'size', 152 | type: createNullableType(FloatType), 153 | resolve: ({ size }) => size, 154 | }); 155 | 156 | // Field with arguments wrong type 157 | PersonType 158 | .field({ 159 | name: 'image', 160 | type: StringType, 161 | args: { 162 | width: { 163 | type: IntegerType, 164 | }, 165 | height: { 166 | type: createNullableType(IntegerType), 167 | }, 168 | }, 169 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 170 | url: imageURL, 171 | width, 172 | height, 173 | } : null, 174 | }); 175 | 176 | // Arguments wrong type 1 177 | PersonType 178 | .field({ 179 | name: 'image', 180 | type: ImageType, 181 | args: { 182 | width: { 183 | type: StringType, 184 | }, 185 | height: { 186 | type: createNullableType(IntegerType), 187 | }, 188 | }, 189 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 190 | url: imageURL, 191 | width, 192 | height, 193 | } : null, 194 | }); 195 | 196 | // Arguments wrong type 2 197 | PersonType 198 | .field({ 199 | name: 'image', 200 | type: ImageType, 201 | args: { 202 | width: { 203 | type: IntegerType, 204 | }, 205 | height: { 206 | type: createNullableType(StringType), 207 | }, 208 | }, 209 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 210 | url: imageURL, 211 | width, 212 | height, 213 | } : null, 214 | }); 215 | 216 | // Missing arguments definition 217 | PersonType 218 | .field({ 219 | name: 'image', 220 | type: ImageType, 221 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 222 | url: imageURL, 223 | width, 224 | height, 225 | } : null, 226 | }); 227 | 228 | // Missing single argument 1 229 | PersonType 230 | .field({ 231 | name: 'image', 232 | type: ImageType, 233 | args: { 234 | height: { 235 | type: createNullableType(IntegerType), 236 | }, 237 | }, 238 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 239 | url: imageURL, 240 | width, 241 | height, 242 | } : null, 243 | }); 244 | 245 | // Missing single argument 2 246 | PersonType 247 | .field({ 248 | name: 'image', 249 | type: ImageType, 250 | args: { 251 | width: { 252 | type: IntegerType, 253 | }, 254 | }, 255 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 256 | url: imageURL, 257 | width, 258 | height, 259 | } : null, 260 | }); 261 | 262 | // Nullable argument in non-null position 263 | PersonType 264 | .field({ 265 | name: 'image', 266 | type: ImageType, 267 | args: { 268 | width: { 269 | type: createNullableType(IntegerType), 270 | }, 271 | height: { 272 | type: createNullableType(IntegerType), 273 | }, 274 | }, 275 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 276 | url: imageURL, 277 | width, 278 | height, 279 | } : null, 280 | }); 281 | 282 | // No generic arguments type 283 | PersonType 284 | .field({ 285 | name: 'image', 286 | type: ImageType, 287 | args: { 288 | width: { 289 | type: IntegerType, 290 | }, 291 | height: { 292 | type: createNullableType(IntegerType), 293 | }, 294 | }, 295 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 296 | url: imageURL, 297 | width, 298 | height, 299 | } : null, 300 | }); 301 | 302 | // Extra argument definitions 303 | PersonType 304 | .field({ 305 | name: 'image', 306 | type: ImageType, 307 | args: { 308 | width: { 309 | type: IntegerType, 310 | }, 311 | height: { 312 | type: createNullableType(IntegerType), 313 | }, 314 | x: { type: StringType }, 315 | y: { type: StringType }, 316 | z: { type: StringType }, 317 | }, 318 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 319 | url: imageURL, 320 | width, 321 | height, 322 | } : null, 323 | }); 324 | 325 | // Extra argument definitions don’t type-check 326 | PersonType 327 | .field({ 328 | name: 'image', 329 | type: ImageType, 330 | args: { 331 | width: { 332 | type: IntegerType, 333 | }, 334 | height: { 335 | type: createNullableType(IntegerType), 336 | }, 337 | x: { type: StringType }, 338 | y: { type: StringType }, 339 | z: { type: StringType }, 340 | }, 341 | resolve: ({ imageURL }, { width, height, x, y, z }) => imageURL ? { 342 | url: imageURL, 343 | width, 344 | height, 345 | } : null, 346 | }); 347 | 348 | // Argument input type passes 349 | PersonType 350 | .field({ 351 | name: 'image', 352 | type: ImageType, 353 | args: { 354 | width: { 355 | type: (null as any) as StrongInputType, 356 | }, 357 | height: { 358 | type: createNullableType(IntegerType), 359 | }, 360 | }, 361 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 362 | url: imageURL, 363 | width, 364 | height, 365 | } : null, 366 | }); 367 | 368 | // Argument output type fails 369 | PersonType 370 | .field({ 371 | name: 'image', 372 | type: ImageType, 373 | args: { 374 | width: { 375 | type: (null as any) as StrongOutputType, 376 | }, 377 | height: { 378 | type: createNullableType(IntegerType), 379 | }, 380 | }, 381 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 382 | url: imageURL, 383 | width, 384 | height, 385 | } : null, 386 | }); 387 | 388 | // Argument input-output type fails 389 | PersonType 390 | .field({ 391 | name: 'image', 392 | type: ImageType, 393 | args: { 394 | width: { 395 | type: (null as any) as StrongInputOutputType, 396 | }, 397 | height: { 398 | type: createNullableType(IntegerType), 399 | }, 400 | }, 401 | resolve: ({ imageURL }, { width, height }) => imageURL ? { 402 | url: imageURL, 403 | width, 404 | height, 405 | } : null, 406 | }); 407 | 408 | // Type with context passes 409 | PersonTypeWithContext 410 | .field({ 411 | name: 'smallImage', 412 | type: ImageType, 413 | resolve: ({ imageURL }, args, context) => 414 | imageURL ? context.ImageService.cropImage(imageURL, 50) : null, 415 | }); 416 | 417 | // Type without context fails 418 | PersonType 419 | .field({ 420 | name: 'smallImage', 421 | type: ImageType, 422 | resolve: ({ imageURL }, args, context) => 423 | imageURL ? context.ImageService.cropImage(imageURL, 50) : null, 424 | }); 425 | 426 | // Un-thunked recursion fails 427 | { 428 | const type: StrongOutputType = createObjectType({ 429 | name: 'foo', 430 | }) 431 | .fieldNonNull({ 432 | name: 'recursive', 433 | type, 434 | resolve: value => value, 435 | }); 436 | } 437 | 438 | // Thunked recursion passes 439 | { 440 | const type: StrongOutputType = createObjectType({ 441 | name: 'foo', 442 | }) 443 | .fieldNonNull({ 444 | name: 'recursive', 445 | type: () => type, 446 | resolve: value => value, 447 | }); 448 | } 449 | 450 | // Returns a promise passes 451 | ImageType 452 | .field({ 453 | name: 'size', 454 | type: FloatType, 455 | resolve: ({ size }) => Promise.resolve(size), 456 | }); 457 | 458 | // Returns a promise of a different type fails 459 | ImageType 460 | .field({ 461 | name: 'size', 462 | type: FloatType, 463 | resolve: () => Promise.resolve('not a number!'), 464 | }); 465 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/scalar-fixture.ts: -------------------------------------------------------------------------------- 1 | import { StrongInputType, StrongOutputType, createScalarType } from '../../index'; 2 | 3 | // Works for just output types 4 | { 5 | const type = createScalarType({ 6 | name: 'foo', 7 | serialize: value => parseInt(value, 10), 8 | }); 9 | } 10 | 11 | // Cannot have `parseValue` without `parseLiteral` 12 | { 13 | const type = createScalarType({ 14 | name: 'foo', 15 | serialize: value => parseInt(value, 10), 16 | parseValue: value => String(value), 17 | }); 18 | } 19 | 20 | // Cannot have `parseLiteral` without `parseValue` 21 | { 22 | const type = createScalarType({ 23 | name: 'foo', 24 | serialize: value => parseInt(value, 10), 25 | parseLiteral: value => value.kind, 26 | }); 27 | } 28 | 29 | // Will not let an output type be an input type 30 | { 31 | const type1: StrongOutputType = createScalarType({ 32 | name: 'foo', 33 | serialize: value => parseInt(value, 10), 34 | }); 35 | 36 | const type2: StrongInputType = createScalarType({ 37 | name: 'foo', 38 | serialize: value => parseInt(value, 10), 39 | }); 40 | } 41 | 42 | // Will let an input-output type be both an input and output type 43 | { 44 | const type1: StrongOutputType = createScalarType({ 45 | name: 'foo', 46 | serialize: value => parseInt(value, 10), 47 | parseValue: value => String(value), 48 | parseLiteral: value => value.kind, 49 | }); 50 | 51 | const type2: StrongInputType = createScalarType({ 52 | name: 'foo', 53 | serialize: value => parseInt(value, 10), 54 | parseValue: value => String(value), 55 | parseLiteral: value => value.kind, 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/schema-fixture.ts: -------------------------------------------------------------------------------- 1 | import { createSchema, StrongObjectType } from '../../index'; 2 | 3 | // Execute works fine with correct value and context 4 | { 5 | const schema = createSchema({ 6 | query: null as any, 7 | }); 8 | 9 | schema.execute('', 1, 2); 10 | } 11 | 12 | // Execute fails with incorrect value 13 | { 14 | const schema = createSchema({ 15 | query: null as any, 16 | }); 17 | 18 | schema.execute('', 'string', 2); 19 | } 20 | 21 | // Execute fails with incorrect context 22 | { 23 | const schema = createSchema({ 24 | query: null as any, 25 | }); 26 | 27 | schema.execute('', 1, 'string'); 28 | } 29 | 30 | // Type works fine with correct value and context 31 | { 32 | const query: StrongObjectType = null as any; 33 | 34 | const schema = createSchema({ 35 | query, 36 | }); 37 | } 38 | 39 | // Type fails with incorrect value 40 | { 41 | const query: StrongObjectType = null as any; 42 | 43 | const schema = createSchema({ 44 | query, 45 | }); 46 | } 47 | 48 | // Type fails with incorrect context 49 | { 50 | const query: StrongObjectType = null as any; 51 | 52 | const schema = createSchema({ 53 | query, 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/wrap-fixture.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInputType, GraphQLOutputType, GraphQLInputObjectType, GraphQLObjectType, GraphQLEnumType } from 'graphql'; 2 | import { StrongInputType, StrongOutputType, wrapWeakType } from '../../index'; 3 | 4 | const anyType: any = null; 5 | 6 | // Input type will not wrap to output type 1 7 | { 8 | const type = wrapWeakType<{}>(anyType as GraphQLInputType); 9 | const input: StrongInputType<{}> = type; 10 | const output: StrongOutputType<{}> = type; 11 | } 12 | 13 | // Input type will not wrap to output type 2 14 | { 15 | const type = wrapWeakType<{}>(anyType as GraphQLInputObjectType); 16 | const input: StrongInputType<{}> = type; 17 | const output: StrongOutputType<{}> = type; 18 | } 19 | 20 | // Output type will not wrap to input type 1 21 | { 22 | const type = wrapWeakType<{}>(anyType as GraphQLOutputType); 23 | const input: StrongInputType<{}> = type; 24 | const output: StrongOutputType<{}> = type; 25 | } 26 | 27 | // Output type will not wrap to input type 2 28 | { 29 | const type = wrapWeakType<{}>(anyType as GraphQLObjectType); 30 | const input: StrongInputType<{}> = type; 31 | const output: StrongOutputType<{}> = type; 32 | } 33 | 34 | // Input/output types will wrap to both input and outputs 1 35 | { 36 | const type = wrapWeakType<{}>(anyType as (GraphQLInputType & GraphQLOutputType)); 37 | const input: StrongInputType<{}> = type; 38 | const output: StrongOutputType<{}> = type; 39 | } 40 | 41 | // Input/output types will wrap to both input and outputs 2 42 | { 43 | const type = wrapWeakType<{}>(anyType as GraphQLEnumType); 44 | const input: StrongInputType<{}> = type; 45 | const output: StrongOutputType<{}> = type; 46 | } 47 | -------------------------------------------------------------------------------- /src/__tests__/object-test.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLNonNull, GraphQLString } from 'graphql'; 2 | import { StrongOutputType, StrongInputType } from '../type'; 3 | import { IntegerType } from '../wrap'; 4 | import { createObjectType } from '../object'; 5 | 6 | const mockOutputType = (): StrongOutputType => ({ 7 | _strongType: true, 8 | _strongOutputType: true, 9 | _strongValue: null, 10 | getWeakType: () => GraphQLString, 11 | getWeakOutputType: () => GraphQLString, 12 | nullable: () => mockOutputType(), 13 | }); 14 | 15 | const mockInputType = (): StrongInputType => ({ 16 | _strongType: true, 17 | _strongInputType: true, 18 | _strongValue: null, 19 | getWeakType: () => GraphQLString, 20 | getWeakInputType: () => GraphQLString, 21 | nullable: () => mockInputType(), 22 | }); 23 | 24 | test('will set the right name and description', () => { 25 | const name = 'foo'; 26 | const description = 'bar'; 27 | expect(createObjectType({ name }).ofType.name).toBe(name); 28 | expect(createObjectType({ name, description }).ofType.name).toBe(name); 29 | expect(createObjectType({ name, description }).ofType.description).toBe(description); 30 | }); 31 | 32 | test('.nullable() will return the nullable version of the type', () => { 33 | const type = createObjectType({ name: 'foo' }); 34 | expect(type.nullable()).toBe(type.ofType); 35 | }); 36 | 37 | test('.getWeakType() returns self', () => { 38 | const type = createObjectType({ name: 'foo' }); 39 | expect(type.getWeakType()).toBe(type); 40 | }); 41 | 42 | test('.getWeakOutputType() returns self', () => { 43 | const type = createObjectType({ name: 'foo' }); 44 | expect(type.getWeakOutputType()).toBe(type); 45 | }); 46 | 47 | test('.nullable().getWeakType() returns nullable type', () => { 48 | const type = createObjectType({ name: 'foo' }); 49 | expect(type.nullable().getWeakType()).toBe(type.ofType); 50 | }); 51 | 52 | test('.nullable().getWeakOutputType() returns nullable type', () => { 53 | const type = createObjectType({ name: 'foo' }); 54 | expect(type.nullable().getWeakOutputType()).toBe(type.ofType); 55 | }); 56 | 57 | test('.field() will create a new object type', () => { 58 | const type = createObjectType({ name: 'foo' }); 59 | expect(type.field({ name: 'bar', type: mockOutputType(), resolve: () => 'baz' })).not.toBe(type); 60 | }); 61 | 62 | test('.field().field() will create a new object type', () => { 63 | const type = createObjectType({ name: 'foo' }).field({ name: 'bar', type: mockOutputType(), resolve: () => 'baz' }); 64 | expect(type.field({ name: 'buz', type: mockOutputType(), resolve: () => 'jit' })).not.toBe(type); 65 | }); 66 | 67 | test('.field() will call .nullable() on the field type', () => { 68 | const outputType = mockOutputType(); 69 | const nullableMock = jest.fn(() => mockOutputType()); 70 | outputType.nullable = nullableMock; 71 | createObjectType({ name: 'foo' }).field({ name: 'bar', type: outputType, resolve: () => 'baz' }).ofType.getFields(); 72 | expect(nullableMock.mock.calls).toEqual([[]]); 73 | }); 74 | 75 | test('.fieldNonNull() will not call .nullable() on the field type', () => { 76 | const outputType = mockOutputType(); 77 | const nullableMock = jest.fn(() => mockOutputType); 78 | outputType.nullable = nullableMock; 79 | createObjectType({ name: 'foo' }).fieldNonNull({ name: 'bar', type: outputType, resolve: () => 'baz' }); 80 | expect(nullableMock.mock.calls).toEqual([]); 81 | }); 82 | 83 | test('.field() will throw if there is already a field of that name', () => { 84 | expect(() => 85 | createObjectType({ name: 'foo' }) 86 | .field({ name: 'bar', type: mockOutputType(), resolve: () => 'baz' }) 87 | .field({ name: 'bar', type: mockOutputType(), resolve: () => 'buz' }), 88 | ).toThrowError('Type \'foo\' already has a field named \'bar\'.'); 89 | }); 90 | 91 | test('.fieldNonNull() will throw if there is already a field of that name', () => { 92 | expect(() => 93 | createObjectType({ name: 'foo' }) 94 | .fieldNonNull({ name: 'bar', type: mockOutputType(), resolve: () => 'baz' }) 95 | .fieldNonNull({ name: 'bar', type: mockOutputType(), resolve: () => 'buz' }), 96 | ).toThrowError('Type \'foo\' already has a field named \'bar\'.'); 97 | }); 98 | 99 | test('native object type fields will return a map with the correct static fields', () => { 100 | const fieldType1 = mockOutputType(); 101 | const fieldType2 = mockOutputType(); 102 | 103 | const type = 104 | createObjectType({ name: 'foo' }) 105 | .field({ name: 'bar', type: fieldType1, resolve: () => 'baz', description: 'description 1', deprecationReason: 'deprecation reason 1' }) 106 | .field({ name: 'buz', type: fieldType2, resolve: () => 'jit', description: 'description 2', deprecationReason: 'deprecation reason 2' }); 107 | 108 | const fields = type.ofType.getFields(); 109 | 110 | expect(Object.keys(fields)).toEqual(['bar', 'buz']); 111 | expect(fields['bar'].description).toBe('description 1'); 112 | expect(fields['buz'].description).toBe('description 2'); 113 | expect(fields['bar'].deprecationReason).toBe('deprecation reason 1'); 114 | expect(fields['buz'].deprecationReason).toBe('deprecation reason 2'); 115 | expect(Object.keys(fields['bar'].args)).toEqual([]); 116 | expect(Object.keys(fields['buz'].args)).toEqual([]); 117 | }); 118 | 119 | test('native object type fields will call for the field type’s output type', () => { 120 | const fieldType1 = mockOutputType(); 121 | const fieldType2 = mockOutputType(); 122 | const fieldOutputType1 = Object.create(GraphQLString); 123 | const fieldOutputType2 = Object.create(GraphQLString); 124 | const weakOutputTypeMock1 = jest.fn(() => fieldOutputType1); 125 | const weakOutputTypeMock2 = jest.fn(() => fieldOutputType2); 126 | fieldType1.getWeakOutputType = weakOutputTypeMock1; 127 | fieldType2.getWeakOutputType = weakOutputTypeMock2; 128 | 129 | const type = 130 | createObjectType({ name: 'foo' }) 131 | .fieldNonNull({ name: 'bar', type: fieldType1, resolve: () => 'baz' }) 132 | .fieldNonNull({ name: 'buz', type: fieldType2, resolve: () => 'jit' }); 133 | 134 | const fields = type.ofType.getFields(); 135 | 136 | expect(Object.keys(fields)).toEqual(['bar', 'buz']); 137 | expect(weakOutputTypeMock1.mock.calls).toEqual([[]]); 138 | expect(weakOutputTypeMock2.mock.calls).toEqual([[]]); 139 | expect(fields['bar'].type).toBe(fieldOutputType1); 140 | expect(fields['buz'].type).toBe(fieldOutputType2); 141 | }); 142 | 143 | test('native object type fields will pass through arguments into resolve functions', () => { 144 | const fieldType1 = mockOutputType(); 145 | const fieldType2 = mockOutputType(); 146 | const resolve1 = jest.fn(() => 'baz'); 147 | const resolve2 = jest.fn(() => 'jit'); 148 | 149 | const type = 150 | createObjectType({ name: 'foo' }) 151 | .field({ name: 'bar', type: fieldType1, resolve: resolve1 }) 152 | .field({ name: 'buz', type: fieldType2, resolve: resolve2 }); 153 | 154 | const fields = type.ofType.getFields(); 155 | 156 | const source1 = Symbol('source1'); 157 | const args1 = { x: Symbol('args1.x') }; 158 | const context1 = Symbol('context1'); 159 | 160 | const source2 = Symbol('source2'); 161 | const args2 = { x: Symbol('args2.x') }; 162 | const context2 = Symbol('context2'); 163 | 164 | expect(Object.keys(fields)).toEqual(['bar', 'buz']); 165 | expect(fields['bar'].resolve(source1, args1, context1, null as any)).toBe('baz'); 166 | expect(fields['buz'].resolve(source2, args2, context2, null as any)).toBe('jit'); 167 | expect(resolve1.mock.calls).toEqual([[source1, args1, context1]]); 168 | expect(resolve2.mock.calls).toEqual([[source2, args2, context2]]); 169 | }); 170 | 171 | test('native object type fields will return fields with correct args', () => { 172 | const fieldType1 = mockOutputType(); 173 | const fieldType2 = mockOutputType(); 174 | 175 | const type = 176 | createObjectType({ name: 'foo' }) 177 | .field({ 178 | name: 'bar', 179 | type: fieldType1, 180 | resolve: () => 'baz', 181 | args: { 182 | x: { type: mockInputType(), defaultValue: 'buz', description: 'description 1' }, 183 | y: { type: mockInputType(), defaultValue: 'jit', description: 'description 2' }, 184 | }, 185 | }); 186 | 187 | const fields = type.ofType.getFields(); 188 | 189 | expect(Object.keys(fields)).toEqual(['bar']); 190 | expect(fields['bar'].args.length).toBe(2); 191 | expect(fields['bar'].args[0].name).toBe('x'); 192 | expect(fields['bar'].args[1].name).toBe('y'); 193 | expect(fields['bar'].args[0].defaultValue).toBe('buz'); 194 | expect(fields['bar'].args[1].defaultValue).toBe('jit'); 195 | expect(fields['bar'].args[0].description).toBe('description 1'); 196 | expect(fields['bar'].args[1].description).toBe('description 2'); 197 | }); 198 | 199 | test('.execute() will execute a query against the object type', async() => { 200 | const type = createObjectType({ name: 'Foo' }) 201 | .field({ name: 'a', type: IntegerType, resolve: () => 1 }) 202 | .field({ name: 'b', type: IntegerType, resolve: () => 2 }) 203 | .field({ name: 'c', type: IntegerType, resolve: () => 3 }); 204 | 205 | expect(await type.execute(` 206 | { 207 | x: a 208 | y: b 209 | z: c 210 | ...foo 211 | } 212 | 213 | fragment foo on Foo { 214 | a 215 | b 216 | c 217 | } 218 | `, {}, {})).toEqual({ 219 | data: { 220 | x: 1, 221 | y: 2, 222 | z: 3, 223 | a: 1, 224 | b: 2, 225 | c: 3, 226 | }, 227 | }); 228 | }); 229 | 230 | test('.extend() will enhance a type with a function', () => { 231 | const type = createObjectType({ name: 'Foo' }) 232 | .extend(t => t 233 | .field({ name: 'a', type: IntegerType, resolve: () => 1 }) 234 | .field({ name: 'b', type: IntegerType, resolve: () => 2 }) 235 | .field({ name: 'c', type: IntegerType, resolve: () => 3 })); 236 | 237 | const fields = type.ofType.getFields(); 238 | 239 | expect(Object.keys(fields)).toEqual(['a', 'b', 'c']); 240 | }); 241 | -------------------------------------------------------------------------------- /src/__tests__/schema-test.ts: -------------------------------------------------------------------------------- 1 | import { graphql, GraphQLError } from 'graphql'; 2 | import { createObjectType } from '../object'; 3 | import { IntegerType } from '../wrap'; 4 | import { createSchema } from '../schema'; 5 | 6 | const QueryType = createObjectType({ name: 'Query' }) 7 | .field({ name: 'a', type: IntegerType, resolve: () => 1 }) 8 | .field({ name: 'b', type: IntegerType, resolve: () => 2 }) 9 | .field({ name: 'c', type: IntegerType, resolve: () => 3 }); 10 | 11 | const MutationType = createObjectType({ name: 'Mutation' }) 12 | .field({ name: 'a', type: IntegerType, resolve: () => 1 }) 13 | .field({ name: 'b', type: IntegerType, resolve: () => 2 }) 14 | .field({ name: 'c', type: IntegerType, resolve: () => 3 }); 15 | 16 | test('will execute the onExecute function if provided for queries', async() => { 17 | const onExecute = jest.fn(); 18 | const schema = createSchema({ 19 | query: QueryType, 20 | mutation: MutationType, 21 | onExecute, 22 | }); 23 | const source1 = Symbol('source1'); 24 | const source2 = Symbol('source2'); 25 | const context1 = Symbol('context1'); 26 | const context2 = Symbol('context2'); 27 | expect(await graphql(schema, '{ a b c }', source1, context1)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 28 | expect(await graphql(schema, '{ a b c }', source2, context2)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 29 | expect(onExecute.mock.calls.length).toBe(2); 30 | expect(onExecute.mock.calls[0].length).toBe(3); 31 | expect(onExecute.mock.calls[0][0]).toBe(source1); 32 | expect(onExecute.mock.calls[0][1]).toBe(context1); 33 | expect(onExecute.mock.calls[1].length).toBe(3); 34 | expect(onExecute.mock.calls[1][0]).toBe(source2); 35 | expect(onExecute.mock.calls[1][1]).toBe(context2); 36 | }); 37 | 38 | test('will propogate errors thrown in the onExecute function for queries', async() => { 39 | const error = Symbol('error'); 40 | const onExecute = jest.fn(() => { throw error; }); 41 | const schema = createSchema({ 42 | query: QueryType, 43 | mutation: MutationType, 44 | onExecute, 45 | }); 46 | const result1 = await graphql(schema, '{ a b c }'); 47 | const result2 = await graphql(schema, '{ a b c }'); 48 | expect(result1.data).toEqual({ a: null, b: null, c: null }); 49 | expect(result1.errors.length).toBe(3); 50 | expect(result2.data).toEqual({ a: null, b: null, c: null }); 51 | expect(result2.errors.length).toBe(3); 52 | }); 53 | 54 | test('will execute the onExecute function if provided for queries and resolve if it is a promise', async() => { 55 | const onExecute = jest.fn(() => new Promise(resolve => setTimeout(resolve, 5))); 56 | const schema = createSchema({ 57 | query: QueryType, 58 | mutation: MutationType, 59 | onExecute, 60 | }); 61 | const source1 = Symbol('source1'); 62 | const source2 = Symbol('source2'); 63 | const context1 = Symbol('context1'); 64 | const context2 = Symbol('context2'); 65 | expect(await graphql(schema, '{ a b c }', source1, context1)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 66 | expect(await graphql(schema, '{ a b c }', source2, context2)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 67 | expect(onExecute.mock.calls.length).toBe(2); 68 | expect(onExecute.mock.calls[0].length).toBe(3); 69 | expect(onExecute.mock.calls[0][0]).toBe(source1); 70 | expect(onExecute.mock.calls[0][1]).toBe(context1); 71 | expect(onExecute.mock.calls[1].length).toBe(3); 72 | expect(onExecute.mock.calls[1][0]).toBe(source2); 73 | expect(onExecute.mock.calls[1][1]).toBe(context2); 74 | }); 75 | 76 | test('will propogate errors thrown in the onExecute function for queries if it is a rejected promise', async() => { 77 | const error = Symbol('error'); 78 | const onExecute = jest.fn(() => new Promise((resolve, reject) => setTimeout(() => reject(error), 5))); 79 | const schema = createSchema({ 80 | query: QueryType, 81 | mutation: MutationType, 82 | onExecute, 83 | }); 84 | const result1 = await graphql(schema, '{ a b c }'); 85 | const result2 = await graphql(schema, '{ a b c }'); 86 | expect(result1.data).toEqual({ a: null, b: null, c: null }); 87 | expect(result1.errors.length).toBe(3); 88 | expect(result2.data).toEqual({ a: null, b: null, c: null }); 89 | expect(result2.errors.length).toBe(3); 90 | }); 91 | 92 | test('will execute the onExecute function if provided for mutations', async() => { 93 | const onExecute = jest.fn(); 94 | const schema = createSchema({ 95 | query: QueryType, 96 | mutation: MutationType, 97 | onExecute, 98 | }); 99 | const source1 = Symbol('source1'); 100 | const source2 = Symbol('source2'); 101 | const context1 = Symbol('context1'); 102 | const context2 = Symbol('context2'); 103 | expect(await graphql(schema, 'mutation { a b c }', source1, context1)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 104 | expect(await graphql(schema, 'mutation { a b c }', source2, context2)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 105 | expect(onExecute.mock.calls.length).toBe(2); 106 | expect(onExecute.mock.calls[0].length).toBe(3); 107 | expect(onExecute.mock.calls[0][0]).toBe(source1); 108 | expect(onExecute.mock.calls[0][1]).toBe(context1); 109 | expect(onExecute.mock.calls[1].length).toBe(3); 110 | expect(onExecute.mock.calls[1][0]).toBe(source2); 111 | expect(onExecute.mock.calls[1][1]).toBe(context2); 112 | }); 113 | 114 | test('will propogate errors thrown in the onExecute function for mutations', async() => { 115 | const error = Symbol('error'); 116 | const onExecute = jest.fn(() => { throw error; }); 117 | const schema = createSchema({ 118 | query: QueryType, 119 | mutation: MutationType, 120 | onExecute, 121 | }); 122 | const result1 = await graphql(schema, 'mutation { a b c }'); 123 | const result2 = await graphql(schema, 'mutation { a b c }'); 124 | expect(result1.data).toEqual({ a: null, b: null, c: null }); 125 | expect(result1.errors.length).toBe(3); 126 | expect(result2.data).toEqual({ a: null, b: null, c: null }); 127 | expect(result2.errors.length).toBe(3); 128 | }); 129 | 130 | test('will execute the onExecute function if provided for mutations and resolve if it is a promise', async() => { 131 | const onExecute = jest.fn(() => new Promise(resolve => setTimeout(resolve, 5))); 132 | const schema = createSchema({ 133 | query: QueryType, 134 | mutation: MutationType, 135 | onExecute, 136 | }); 137 | const source1 = Symbol('source1'); 138 | const source2 = Symbol('source2'); 139 | const context1 = Symbol('context1'); 140 | const context2 = Symbol('context2'); 141 | expect(await graphql(schema, 'mutation { a b c }', source1, context1)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 142 | expect(await graphql(schema, 'mutation { a b c }', source2, context2)).toEqual({ data: { a: 1, b: 2, c: 3 } }); 143 | expect(onExecute.mock.calls.length).toBe(2); 144 | expect(onExecute.mock.calls[0].length).toBe(3); 145 | expect(onExecute.mock.calls[0][0]).toBe(source1); 146 | expect(onExecute.mock.calls[0][1]).toBe(context1); 147 | expect(onExecute.mock.calls[1].length).toBe(3); 148 | expect(onExecute.mock.calls[1][0]).toBe(source2); 149 | expect(onExecute.mock.calls[1][1]).toBe(context2); 150 | }); 151 | 152 | test('will propogate errors thrown in the onExecute function for mutations if it is a rejected promise', async() => { 153 | const error = Symbol('error'); 154 | const onExecute = jest.fn(() => new Promise((resolve, reject) => setTimeout(() => reject(error), 5))); 155 | const schema = createSchema({ 156 | query: QueryType, 157 | mutation: MutationType, 158 | onExecute, 159 | }); 160 | const result1 = await graphql(schema, 'mutation { a b c }'); 161 | const result2 = await graphql(schema, 'mutation { a b c }'); 162 | expect(result1.data).toEqual({ a: null, b: null, c: null }); 163 | expect(result1.errors.length).toBe(3); 164 | expect(result2.data).toEqual({ a: null, b: null, c: null }); 165 | expect(result2.errors.length).toBe(3); 166 | }); 167 | -------------------------------------------------------------------------------- /src/args.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types used for arguments that are shared by objects, interfaces, directives 3 | * and other GraphQL constructs that use arguments. 4 | */ 5 | 6 | import { GraphQLFieldConfigArgumentMap } from 'graphql'; 7 | import { StrongInputType } from './type'; 8 | 9 | /** 10 | * A type which represents the GraphQL type definition of the argument 11 | * TypeScript type provided. 12 | */ 13 | export type StrongArgsConfig = { 14 | [TArg in keyof TArgs]: StrongArgConfig 15 | }; 16 | 17 | /** 18 | * A type which represents a single argument configuration. 19 | */ 20 | export type StrongArgConfig = { 21 | readonly type: StrongInputType, 22 | readonly defaultValue?: TValue, 23 | readonly description?: string | undefined, 24 | }; 25 | 26 | /** 27 | * Turns a strong argument config into a weak argument map that can be fed into 28 | * GraphQL.js. 29 | */ 30 | export function getWeakArgsMap(args: StrongArgsConfig): GraphQLFieldConfigArgumentMap { 31 | const weakArgs: GraphQLFieldConfigArgumentMap = {}; 32 | for (const argName of Object.keys(args)) { 33 | const argConfig = args[argName]; 34 | weakArgs[argName] = { 35 | type: argConfig.type.getWeakInputType(), 36 | defaultValue: argConfig.defaultValue, 37 | description: argConfig.description, 38 | }; 39 | } 40 | return weakArgs; 41 | } 42 | -------------------------------------------------------------------------------- /src/description.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A utility function for creating GraphQL descriptions out of a multiline 3 | * template string. 4 | * 5 | * It trims whitespace from the beginning and end of the string in addition to 6 | * removing indentation. 7 | */ 8 | export function trimDescription(description: string): string { 9 | return description.replace(/^ +/gm, '').trim(); 10 | } 11 | 12 | /** 13 | * Runs `trimDescription` on all properties named `description` deeply in a 14 | * config object. 15 | * 16 | * Creates a new config object instead of mutating the config object passed in. 17 | * 18 | * We only trim descriptions on plain JavaScript objects. 19 | */ 20 | export function trimDescriptionsInConfig(config: T): T { 21 | const nextConfig: { [key: string]: any } = {}; 22 | 23 | // Iterate through every key in the config object. 24 | for (const key of Object.keys(config)) { 25 | const value = config[key]; 26 | 27 | // If the value at this key is an object we need to recurse this function. 28 | if (value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) { 29 | nextConfig[key] = trimDescriptionsInConfig(value); 30 | } 31 | // If this key is `description` and the value is a string then we need to 32 | // trim the description. 33 | else if (key === 'description' && typeof value === 'string') { 34 | nextConfig[key] = trimDescription(value); 35 | } 36 | // Otherwise just copy the value over to the new config. 37 | else { 38 | nextConfig[key] = value; 39 | } 40 | } 41 | 42 | return nextConfig as T; 43 | } 44 | -------------------------------------------------------------------------------- /src/enum.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNonNull, GraphQLEnumType } from 'graphql'; 2 | import { StrongInputOutputType } from './type'; 3 | import { trimDescriptionsInConfig } from './description'; 4 | 5 | /** 6 | * Creates a type-safe non-null enum GraphQL type. 7 | */ 8 | export function createEnumType (config: StrongEnumTypeConfig): StrongInputOutputType { 9 | return new StrongEnumType(new StrongNullableEnumType(trimDescriptionsInConfig(config))); 10 | } 11 | 12 | /** 13 | * The configuration for an enum type. 14 | */ 15 | export type StrongEnumTypeConfig = { 16 | readonly name: string, 17 | readonly description?: string | undefined, 18 | readonly values: { 19 | readonly [valueName: string]: { 20 | readonly value: TValue, 21 | readonly description?: string | undefined, 22 | readonly deprecationReason?: string | undefined, 23 | }, 24 | }, 25 | }; 26 | 27 | /** 28 | * The non-null strong GraphQL enum type object. 29 | */ 30 | class StrongEnumType 31 | extends GraphQLNonNull> 32 | implements StrongInputOutputType { 33 | // The required type flags. 34 | public readonly _strongType: true = true; 35 | public readonly _strongInputType: true = true; 36 | public readonly _strongOutputType: true = true; 37 | public readonly _strongValue: TValue = undefined as any; 38 | 39 | constructor(nullableType: StrongNullableEnumType) { 40 | super(nullableType); 41 | } 42 | 43 | // The required type conversion methods. 44 | public getWeakType(): this { return this; } 45 | public getWeakInputType(): this { return this; } 46 | public getWeakOutputType(): this { return this; } 47 | 48 | /** 49 | * Returns the inner nullable variation of this type. 50 | */ 51 | public nullable(): StrongInputOutputType { 52 | return this.ofType; 53 | } 54 | } 55 | 56 | /** 57 | * The nullable sstrong GraphQL enum type object. 58 | */ 59 | class StrongNullableEnumType 60 | extends GraphQLEnumType 61 | implements StrongInputOutputType { 62 | // The required type flags. 63 | public readonly _strongType: true = true; 64 | public readonly _strongInputType: true = true; 65 | public readonly _strongOutputType: true = true; 66 | public readonly _strongValue: TValue | null | undefined = undefined as any; 67 | 68 | constructor(config: StrongEnumTypeConfig) { 69 | super(config); 70 | } 71 | 72 | // The required type conversion methods. 73 | public getWeakType(): this { return this; } 74 | public getWeakInputType(): this { return this; } 75 | public getWeakOutputType(): this { return this; } 76 | 77 | /** 78 | * Returns self. 79 | */ 80 | public nullable(): this { 81 | return this; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | StrongType, 3 | StrongInputType, 4 | StrongOutputType, 5 | StrongInputOutputType, 6 | } from './type'; 7 | 8 | export { 9 | createScalarType, 10 | } from './scalar'; 11 | 12 | export { 13 | createObjectType, 14 | StrongObjectTypeConfig, 15 | StrongFieldConfig, 16 | StrongFieldConfigWithoutArgs, 17 | StrongFieldConfigWithArgs, 18 | StrongObjectType, 19 | StrongNullableObjectType, 20 | } from './object'; 21 | 22 | export { 23 | createInterfaceType, 24 | StrongInterfaceFieldMap, 25 | StrongInterfaceTypeConfig, 26 | StrongInterfaceFieldMapConfig, 27 | StrongInterfaceFieldConfig, 28 | StrongInterfaceImplementation, 29 | StrongInterfaceFieldImplementation, 30 | StrongInterfaceType, 31 | StrongNullableInterfaceType, 32 | } from './interface'; 33 | 34 | export { 35 | createEnumType, 36 | } from './enum'; 37 | 38 | export { 39 | createListType, 40 | } from './list'; 41 | 42 | export { 43 | createNullableType, 44 | } from './nullable'; 45 | 46 | export { 47 | wrapWeakType, 48 | IntegerType, 49 | FloatType, 50 | StringType, 51 | BooleanType, 52 | IDType, 53 | } from './wrap'; 54 | 55 | export { 56 | createSchema, 57 | } from './schema'; 58 | 59 | export { 60 | StrongArgsConfig, 61 | StrongArgConfig, 62 | } from './args'; 63 | 64 | export { 65 | trimDescription, 66 | } from './description'; 67 | -------------------------------------------------------------------------------- /src/interface.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNonNull, GraphQLInterfaceType, GraphQLFieldConfigMap } from 'graphql'; 2 | import { StrongOutputType } from './type'; 3 | import { trimDescriptionsInConfig } from './description'; 4 | import { StrongArgsConfig, getWeakArgsMap } from './args'; 5 | import { StrongObjectType } from './object'; 6 | 7 | /** 8 | * Creates a new strong GraphQL interface type. In addition to the runtime 9 | * configuration object there is also one important type parameter: `TFieldMap`. 10 | * `TFieldMap` will be used to compute a lot of things involving this interface. 11 | * 12 | * This returns the non-null interface type. To get the nullable type just call 13 | * `.nullable()`. 14 | */ 15 | export function createInterfaceType(config: StrongInterfaceTypeConfig<{}, TFieldMap>): StrongInterfaceType<{}, TFieldMap>; 16 | export function createInterfaceType(config: StrongInterfaceTypeConfig): StrongInterfaceType; 17 | export function createInterfaceType(config: StrongInterfaceTypeConfig): StrongInterfaceType { 18 | return new StrongInterfaceType(new StrongNullableInterfaceType(trimDescriptionsInConfig(config))); 19 | } 20 | 21 | /** 22 | * The structure of the type we want as a type argument to 23 | * `createInterfaceType`. 24 | * 25 | * This type uniquely *does not* represent any runtime type. Instead it only 26 | * represents an abstract type that will be transformed into other types used in 27 | * defining and implementing interfaces. 28 | */ 29 | export type StrongInterfaceFieldMap = { 30 | [fieldName: string]: { 31 | type: any, 32 | args?: { [argName: string]: any }, 33 | }, 34 | }; 35 | 36 | /** 37 | * The configuration object to be used when creating interface types. It 38 | * requires `resolveType` and `fields`. 39 | */ 40 | export type StrongInterfaceTypeConfig = { 41 | readonly name: string, 42 | readonly description?: string | undefined, 43 | readonly resolveType: (value: TValue) => StrongObjectType, 44 | readonly fields: StrongInterfaceFieldMapConfig, 45 | }; 46 | 47 | /** 48 | * The type for a fields configuration map. 49 | */ 50 | export type StrongInterfaceFieldMapConfig = { 51 | readonly [TField in keyof TFieldMap]: StrongInterfaceFieldConfig 52 | }; 53 | 54 | /** 55 | * The configuration type for a single interface field. 56 | */ 57 | export type StrongInterfaceFieldConfig = { 58 | readonly description?: string | undefined, 59 | readonly deprecationReason?: string | undefined, 60 | readonly type: StrongOutputType, 61 | readonly args?: StrongArgsConfig, 62 | }; 63 | 64 | /** 65 | * The object that users will use to implement an interface on a strong object 66 | * type. It is a map of field names to resolver functions. 67 | */ 68 | export type StrongInterfaceImplementation = { 69 | readonly [TField in keyof TFieldMap]: StrongInterfaceFieldImplementation 70 | }; 71 | 72 | /** 73 | * The resolver function that is used to implement an interface on a strong 74 | * object type. 75 | */ 76 | export type StrongInterfaceFieldImplementation = 77 | (source: TSourceValue, args: TArgs, context: TContext) => TValue | Promise; 78 | 79 | /** 80 | * The interface type class created by `createInterfaceType`. It is 81 | * non-null, to get the nullable variant just call `.nullable()`. 82 | */ 83 | export 84 | class StrongInterfaceType 85 | extends GraphQLNonNull> 86 | implements StrongOutputType { 87 | // The required type flags. 88 | public readonly _strongType: true = true; 89 | public readonly _strongOutputType: true = true; 90 | public readonly _strongValue: TValue = undefined as any; 91 | 92 | constructor(nullableType: StrongNullableInterfaceType) { 93 | super(nullableType); 94 | } 95 | 96 | // The required type conversion methods. 97 | public getWeakType(): this { return this; } 98 | public getWeakOutputType(): this { return this; } 99 | 100 | /** 101 | * Returns the inner nullable version of this type without mutating anything. 102 | */ 103 | public nullable(): StrongOutputType { 104 | return this.ofType; 105 | } 106 | 107 | /** 108 | * Returns the configuration object for fields on this interface. 109 | * 110 | * This method is private and should only be called inside of 111 | * `graphql-strong`. 112 | */ 113 | public _getFieldConfigMap(): StrongInterfaceFieldMapConfig { 114 | return this.ofType._getFieldConfigMap(); 115 | } 116 | } 117 | 118 | /** 119 | * The class for the nullable variant of the interface type. Because nullability 120 | * is reversed in `graphql-strong`, this is what actually extends the GraphQL.js 121 | * interface type. 122 | */ 123 | export 124 | class StrongNullableInterfaceType 125 | extends GraphQLInterfaceType 126 | implements StrongOutputType { 127 | // The required type flags. 128 | public readonly _strongType: true = true; 129 | public readonly _strongOutputType: true = true; 130 | public readonly _strongValue: TValue | null | undefined = undefined as any; 131 | 132 | private readonly _strongConfig: StrongInterfaceTypeConfig; 133 | 134 | constructor(config: StrongInterfaceTypeConfig) { 135 | super({ 136 | name: config.name, 137 | description: config.description, 138 | resolveType: value => config.resolveType(value).ofType, 139 | // Compute our fields from the fields map we were provided in the config. 140 | // The format we define in our config is pretty similar to the format 141 | // GraphQL.js expects. 142 | fields: (): GraphQLFieldConfigMap => { 143 | const weakFields: GraphQLFieldConfigMap = {}; 144 | for (const fieldName of Object.keys(config.fields)) { 145 | const fieldConfig = config.fields[fieldName]; 146 | weakFields[fieldName] = { 147 | description: fieldConfig.description, 148 | deprecationReason: fieldConfig.deprecationReason, 149 | type: fieldConfig.type.getWeakOutputType(), 150 | args: fieldConfig.args && getWeakArgsMap(fieldConfig.args), 151 | }; 152 | } 153 | return weakFields; 154 | }, 155 | }); 156 | this._strongConfig = config; 157 | } 158 | 159 | // The required type conversion methods. 160 | public getWeakType(): this { return this; } 161 | public getWeakOutputType(): this { return this; } 162 | 163 | /** 164 | * Returns self. 165 | */ 166 | public nullable(): this { 167 | return this; 168 | } 169 | 170 | /** 171 | * Returns the configuration object for fields on this interface. 172 | * 173 | * This method is private and should only be called inside of 174 | * `graphql-strong`. 175 | */ 176 | public _getFieldConfigMap(): StrongInterfaceFieldMapConfig { 177 | return this._strongConfig.fields; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/list.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLList, GraphQLNonNull } from 'graphql'; 2 | import { StrongInputType, StrongOutputType, StrongInputOutputType } from './type'; 3 | 4 | /** 5 | * Creates a strong list type where the inner type is whatever GraphQL strong 6 | * type is passed in. 7 | */ 8 | export function createListType(type: StrongInputOutputType): StrongInputOutputType>; 9 | export function createListType(type: StrongInputType): StrongInputType>; 10 | export function createListType(type: StrongOutputType): StrongOutputType>; 11 | export function createListType(type: StrongInputOutputType): StrongInputOutputType> { 12 | const nullableListType: StrongInputOutputType | null | undefined> = { 13 | _strongType: true, 14 | _strongInputType: true, 15 | _strongOutputType: true, 16 | _strongValue: undefined as any, 17 | getWeakType: () => new GraphQLList(type.getWeakType()), 18 | getWeakInputType: () => new GraphQLList(type.getWeakInputType()), 19 | getWeakOutputType: () => new GraphQLList(type.getWeakOutputType()), 20 | nullable: () => nullableListType, 21 | }; 22 | const listType: StrongInputOutputType> = { 23 | _strongType: true, 24 | _strongInputType: true, 25 | _strongOutputType: true, 26 | _strongValue: undefined as any, 27 | getWeakType: () => new GraphQLNonNull(new GraphQLList(type.getWeakType())), 28 | getWeakInputType: () => new GraphQLNonNull(new GraphQLList(type.getWeakInputType())), 29 | getWeakOutputType: () => new GraphQLNonNull(new GraphQLList(type.getWeakOutputType())), 30 | nullable: () => nullableListType, 31 | }; 32 | return listType; 33 | } 34 | -------------------------------------------------------------------------------- /src/nullable.ts: -------------------------------------------------------------------------------- 1 | import { StrongType, StrongInputType, StrongOutputType, StrongInputOutputType } from './type'; 2 | 3 | /** 4 | * Returns a type which has the same type as the strong GraphQL type passed in, 5 | * except the type also supports null values. 6 | * 7 | * In the standard GraphQL-JS library, all types are nullable by default. 8 | * However, in TypeScript and many other type systems it makes more sense that 9 | * types be non-null by default. This is like `GraphQLNonNull` except it does 10 | * the inverse. 11 | */ 12 | export function createNullableType(type: StrongInputOutputType): StrongInputOutputType; 13 | export function createNullableType(type: StrongInputType): StrongInputType; 14 | export function createNullableType(type: StrongOutputType): StrongOutputType; 15 | export function createNullableType(type: StrongType): StrongType { 16 | return type.nullable(); 17 | } 18 | -------------------------------------------------------------------------------- /src/object.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNonNull, GraphQLObjectType, GraphQLFieldConfigMap, GraphQLSchema, graphql, ExecutionResult } from 'graphql'; 2 | import { StrongOutputType } from './type'; 3 | import { trimDescriptionsInConfig } from './description'; 4 | import { StrongArgsConfig, getWeakArgsMap } from './args'; 5 | import { StrongInterfaceFieldMap, StrongInterfaceType, StrongInterfaceImplementation } from './interface'; 6 | 7 | /** 8 | * Creates a strong GraphQL object type with a fluent builder interface. 9 | * 10 | * The type will be non-null, in order to get the nullable variant of the type 11 | * just call `.nullable()`. 12 | */ 13 | export function createObjectType(config: StrongObjectTypeConfig): StrongObjectType; 14 | export function createObjectType(config: StrongObjectTypeConfig): StrongObjectType; 15 | export function createObjectType(config: StrongObjectTypeConfig): StrongObjectType { 16 | return new StrongObjectType(new StrongNullableObjectType(trimDescriptionsInConfig(config), [], [])); 17 | } 18 | 19 | /** 20 | * A configuration object to be used when creating object types. Any extra 21 | * options will go straight into the type config. 22 | */ 23 | export type StrongObjectTypeConfig = { 24 | readonly name: string, 25 | readonly description?: string | undefined, 26 | }; 27 | 28 | /** 29 | * The configration object for a single field of a strong GraphQL object type. 30 | * Takes a lot of generic type parameters to make sure everything is super safe! 31 | * 32 | * Arguments are optional. 33 | */ 34 | export type StrongFieldConfig = { 35 | readonly name: string, 36 | readonly description?: string | undefined, 37 | readonly deprecationReason?: string | undefined, 38 | readonly type: StrongOutputType | (() => StrongOutputType), 39 | readonly args?: StrongArgsConfig, 40 | readonly resolve: (source: TSourceValue, args: TArgs, context: TContext) => TValue | Promise, 41 | }; 42 | 43 | /** 44 | * A single field configuration except for you don’t need the arguments. 45 | */ 46 | export type StrongFieldConfigWithoutArgs = StrongFieldConfig; 47 | 48 | /** 49 | * A single field configuration except the arguments are required. 50 | */ 51 | export type StrongFieldConfigWithArgs = StrongFieldConfig & { 52 | readonly args: StrongArgsConfig, 53 | }; 54 | 55 | /** 56 | * The object returned by `createObjectType`. It is non-null, to get the 57 | * nullable variant just call `.nullable()`. 58 | */ 59 | // Developers could just instantiate this object directly instead of using 60 | // `createObjectType`, but the function interface feels nicer and allows us to 61 | // add extra features like function overloading. 62 | export 63 | class StrongObjectType 64 | extends GraphQLNonNull> 65 | implements StrongOutputType { 66 | // The required type flags. 67 | public readonly _strongType: true = true; 68 | public readonly _strongOutputType: true = true; 69 | public readonly _strongValue: TValue = undefined as any; 70 | 71 | /** 72 | * A schema created for executing queries against where the query type is this 73 | * object type. 74 | */ 75 | private _schema: GraphQLSchema | null = null; 76 | 77 | /** 78 | * The name of our object type. 79 | */ 80 | public readonly name: string; 81 | 82 | constructor(nullableType: StrongNullableObjectType) { 83 | super(nullableType); 84 | this.name = nullableType.name; 85 | } 86 | 87 | // The required type conversion methods. 88 | public getWeakType(): this { return this; } 89 | public getWeakOutputType(): this { return this; } 90 | 91 | /** 92 | * Returns the inner nullable version of this type without mutating anything. 93 | */ 94 | public nullable(): StrongOutputType { 95 | return this.ofType; 96 | } 97 | 98 | /** 99 | * Returns a new strong GraphQL object type with a new field. This function 100 | * does not mutate the type it was called on. 101 | * 102 | * The field created will have a nullable type. To get a non-null field type 103 | * use `fieldNonNull`. 104 | */ 105 | public field(config: StrongFieldConfigWithoutArgs): StrongObjectType 106 | public field(config: StrongFieldConfigWithArgs): StrongObjectType 107 | public field(config: StrongFieldConfig): StrongObjectType { 108 | return new StrongObjectType(this.ofType._field(config)); 109 | } 110 | 111 | /** 112 | * Returns a new strong GraphQL object type with a new field. This function 113 | * does not mutate the type it was called on. 114 | */ 115 | public fieldNonNull(config: StrongFieldConfigWithoutArgs): StrongObjectType 116 | public fieldNonNull(config: StrongFieldConfigWithArgs): StrongObjectType 117 | public fieldNonNull(config: StrongFieldConfig): StrongObjectType { 118 | return new StrongObjectType(this.ofType._fieldNonNull(config)); 119 | } 120 | 121 | /** 122 | * Implement a strong GraphQL interface defining only the new resolvers. All 123 | * of the descriptions, type, and argument information will be copied over 124 | * from the interface type. 125 | */ 126 | public implement( 127 | interfaceType: StrongInterfaceType, 128 | implementation: StrongInterfaceImplementation, 129 | ): StrongObjectType { 130 | return new StrongObjectType(this.ofType._implement(interfaceType, implementation)); 131 | } 132 | 133 | /** 134 | * Extends the object type be calling a function which takes the object as an 135 | * input and returns an object of the same type. This allows the creation of 136 | * simple extensions that leverage the immutable builder pattern used by this 137 | * library. 138 | */ 139 | public extend(extension: (type: this) => StrongObjectType): StrongObjectType { 140 | return extension(this); 141 | } 142 | 143 | /** 144 | * Creates a new copy of this type. It is the exact same as the type which 145 | * `.clone()` was called on except that the reference is different. 146 | */ 147 | public clone(): StrongObjectType { 148 | return new StrongObjectType(this.ofType.clone()); 149 | } 150 | 151 | /** 152 | * Executes a GraphQL query against this type. The schema used for executing 153 | * this query uses this object type as the query object type. There is no 154 | * mutation or subscription type. 155 | * 156 | * This can be very useful in testing. 157 | */ 158 | public execute(query: string, value: TValue, context: TContext, variables: { [key: string]: any } = {}, operation?: string): Promise { 159 | if (this._schema === null) { 160 | this._schema = new GraphQLSchema({ query: this.ofType }); 161 | } 162 | return graphql(this._schema, query, value, context, variables, operation); 163 | } 164 | } 165 | 166 | /** 167 | * The private nullable implementation of `StrongObjectType`. Because we 168 | * want types to be non-null by default, but in GraphQL types are nullable by 169 | * default this type is also the one that actually extends from 170 | * `GraphQLObjectType`. 171 | */ 172 | export 173 | class StrongNullableObjectType 174 | extends GraphQLObjectType 175 | implements StrongOutputType { 176 | // The required type flags. 177 | public readonly _strongType: true = true; 178 | public readonly _strongOutputType: true = true; 179 | public readonly _strongValue: TValue | null | undefined = undefined as any; 180 | 181 | private readonly _strongConfig: StrongObjectTypeConfig; 182 | private readonly _strongInterfaces: Array>; 183 | private readonly _strongFieldConfigs: Array>; 184 | 185 | constructor( 186 | config: StrongObjectTypeConfig, 187 | interfaces: Array>, 188 | fieldConfigs: Array>, 189 | ) { 190 | super({ 191 | name: config.name, 192 | description: config.description, 193 | // Add all of the nullable versions of our interfaces. 194 | interfaces: interfaces.map(interfaceType => interfaceType.ofType), 195 | // We define a thunk which computes our fields from the fields config 196 | // array we’ve built. 197 | fields: (): GraphQLFieldConfigMap => { 198 | const weakFields: GraphQLFieldConfigMap = {}; 199 | for (const fieldConfig of fieldConfigs) { 200 | weakFields[fieldConfig.name] = { 201 | description: fieldConfig.description, 202 | deprecationReason: fieldConfig.deprecationReason, 203 | type: typeof fieldConfig.type === 'function' ? fieldConfig.type().getWeakOutputType() : fieldConfig.type.getWeakOutputType(), 204 | args: fieldConfig.args && getWeakArgsMap(fieldConfig.args), 205 | resolve: (source, args, context) => fieldConfig.resolve(source, args, context), 206 | }; 207 | } 208 | return weakFields; 209 | }, 210 | }); 211 | this._strongConfig = config; 212 | this._strongInterfaces = interfaces; 213 | this._strongFieldConfigs = fieldConfigs; 214 | } 215 | 216 | // The required type conversion methods. 217 | public getWeakType(): this { return this; } 218 | public getWeakOutputType(): this { return this; } 219 | 220 | /** 221 | * Returns self. 222 | */ 223 | public nullable(): this { 224 | return this; 225 | } 226 | 227 | /** 228 | * Creates a new copy of this type. It is the exact same as the type which 229 | * `.clone()` was called on except that the reference is different. 230 | */ 231 | public clone(): StrongNullableObjectType { 232 | return new StrongNullableObjectType( 233 | this._strongConfig, 234 | this._strongInterfaces, 235 | this._strongFieldConfigs, 236 | ); 237 | } 238 | 239 | /** 240 | * Returns true if we already have a field of this name. 241 | */ 242 | private _hasField(fieldName: string): boolean { 243 | return !!this._strongFieldConfigs.find(({ name }) => name === fieldName); 244 | } 245 | 246 | /** 247 | * Throws an error if we already have a field with the provided name, 248 | * otherwise the function does nothing. 249 | */ 250 | private _assertUniqueFieldName(fieldName: string): void { 251 | if (this._hasField(fieldName)) { 252 | throw new Error(`Type '${this.name}' already has a field named '${fieldName}'.`); 253 | } 254 | } 255 | 256 | /** 257 | * This method is a private implementation detail and should not be used 258 | * outside of `StrongObjectType`! 259 | */ 260 | public _field (config: StrongFieldConfig): StrongNullableObjectType { 261 | this._assertUniqueFieldName(config.name); 262 | return new StrongNullableObjectType( 263 | this._strongConfig, 264 | this._strongInterfaces, 265 | [...this._strongFieldConfigs, trimDescriptionsInConfig({ 266 | ...config, 267 | type: () => typeof config.type === 'function' ? config.type().nullable() : config.type.nullable(), 268 | })], 269 | ); 270 | } 271 | 272 | /** 273 | * This method is a private implementation detail and should not be used 274 | * outside of `StrongObjectType`! 275 | */ 276 | public _fieldNonNull (config: StrongFieldConfig): StrongNullableObjectType { 277 | this._assertUniqueFieldName(config.name); 278 | return new StrongNullableObjectType( 279 | this._strongConfig, 280 | this._strongInterfaces, 281 | [...this._strongFieldConfigs, trimDescriptionsInConfig(config)], 282 | ); 283 | } 284 | 285 | /** 286 | * This method is a private implementation detail and should not be used 287 | * outside of `StrongObjectType`! 288 | */ 289 | public _implement( 290 | interfaceType: StrongInterfaceType, 291 | implementation: StrongInterfaceImplementation, 292 | ): StrongNullableObjectType { 293 | // Get the field config map from our interface. 294 | const fieldConfigMap = interfaceType._getFieldConfigMap(); 295 | // Create all of the object fields from our interface fields and the 296 | // implementation argument. 297 | const fieldConfigs = Object.keys(fieldConfigMap).map>(fieldName => { 298 | // Make sure that this interface field name has not already been taken. 299 | this._assertUniqueFieldName(fieldName); 300 | // Get what we will need to create this field. 301 | const fieldConfig = fieldConfigMap[fieldName]; 302 | const fieldResolver = implementation[fieldName]; 303 | // Create a field. 304 | return trimDescriptionsInConfig({ 305 | name: fieldName, 306 | description: fieldConfig.description, 307 | deprecationReason: fieldConfig.deprecationReason, 308 | type: fieldConfig.type, 309 | args: fieldConfig.args, 310 | resolve: fieldResolver, 311 | }); 312 | }); 313 | // Create a new strong nullable object type with our new fields and our new 314 | // interface. 315 | return new StrongNullableObjectType( 316 | this._strongConfig, 317 | [...this._strongInterfaces, interfaceType], 318 | [...this._strongFieldConfigs, ...fieldConfigs], 319 | ); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/scalar.ts: -------------------------------------------------------------------------------- 1 | import { ValueNode, GraphQLNonNull, GraphQLScalarType } from 'graphql'; 2 | import { StrongOutputType, StrongInputOutputType } from './type'; 3 | import { trimDescriptionsInConfig } from './description'; 4 | 5 | /** 6 | * Creates a GraphQL scalar type. 7 | * 8 | * If you just past a `serialize` function with your config you will get an 9 | * output type. If you pass both a `parseValue` and `parseLiteral` function with 10 | * your config, you will get an input/output type. 11 | */ 12 | export function createScalarType(config: StrongScalarTypeConfigWithoutInput): StrongOutputType; 13 | export function createScalarType(config: StrongScalarTypeConfigWithInput): StrongInputOutputType; 14 | export function createScalarType(config: StrongScalarTypeConfig): StrongInputOutputType { 15 | return new StrongScalarType(new StrongNullableScalarType(trimDescriptionsInConfig(config))); 16 | } 17 | 18 | /** 19 | * The base GraphQL scalar type config. It has optional `parseValue` and 20 | * `parseLiteral` properties. 21 | */ 22 | type StrongScalarTypeConfig = { 23 | readonly name: string, 24 | readonly description?: string | undefined, 25 | readonly serialize: (value: TInternalValue) => TExternalValue, 26 | readonly parseValue?: (value: TExternalValue) => TInternalValue | null, 27 | readonly parseLiteral?: (ast: ValueNode) => TInternalValue | null, 28 | }; 29 | 30 | /** 31 | * A GraphQL scalar type config that does not support input values, only output 32 | * values. 33 | */ 34 | export type StrongScalarTypeConfigWithoutInput = { 35 | readonly name: string, 36 | readonly description?: string | undefined, 37 | readonly serialize: (value: TInternalValue) => TExternalValue, 38 | }; 39 | 40 | /** 41 | * A GraphQL scalar type config that does support input values alongside output 42 | * values. 43 | */ 44 | export type StrongScalarTypeConfigWithInput = { 45 | readonly name: string, 46 | readonly description?: string | undefined, 47 | readonly serialize: (value: TInternalValue) => TExternalValue, 48 | readonly parseValue: (value: TExternalValue) => TInternalValue | null, 49 | readonly parseLiteral: (ast: ValueNode) => TInternalValue | null, 50 | }; 51 | 52 | /** 53 | * The non-null strong GraphQL scalar type object. 54 | */ 55 | class StrongScalarType 56 | extends GraphQLNonNull> 57 | implements StrongInputOutputType { 58 | // The required type flags. 59 | public readonly _strongType: true = true; 60 | public readonly _strongInputType: true = true; 61 | public readonly _strongOutputType: true = true; 62 | public readonly _strongValue: TInternalValue = undefined as any; 63 | 64 | constructor(nullableType: StrongNullableScalarType) { 65 | super(nullableType); 66 | } 67 | 68 | // The required type conversion methods. 69 | public getWeakType(): this { return this; } 70 | public getWeakInputType(): this { return this; } 71 | public getWeakOutputType(): this { return this; } 72 | 73 | /** 74 | * Returns the inner nullable variation of this type. 75 | */ 76 | public nullable(): StrongInputOutputType { 77 | return this.ofType; 78 | } 79 | } 80 | 81 | /** 82 | * The nullable sstrong GraphQL scalar type object. 83 | */ 84 | class StrongNullableScalarType 85 | extends GraphQLScalarType 86 | implements StrongInputOutputType { 87 | // The required type flags. 88 | public readonly _strongType: true = true; 89 | public readonly _strongInputType: true = true; 90 | public readonly _strongOutputType: true = true; 91 | public readonly _strongValue: TInternalValue | null | undefined = undefined as any; 92 | 93 | constructor(config: StrongScalarTypeConfig) { 94 | super(config); 95 | } 96 | 97 | // The required type conversion methods. 98 | public getWeakType(): this { return this; } 99 | public getWeakInputType(): this { return this; } 100 | public getWeakOutputType(): this { return this; } 101 | 102 | /** 103 | * Returns self. 104 | */ 105 | public nullable(): this { 106 | return this; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, graphql, ExecutionResult, GraphQLResolveInfo, defaultFieldResolver, OperationDefinitionNode } from 'graphql'; 2 | import { StrongObjectType } from './object'; 3 | 4 | /** 5 | * Creates a strong, type-safe, GraphQL schema that forces correctness on 6 | * execution. 7 | */ 8 | export function createSchema(config: StrongSchemaConfig): StrongSchema; 9 | export function createSchema(config: StrongSchemaConfig): StrongSchema; 10 | export function createSchema(config: StrongSchemaConfig): StrongSchema { 11 | return new StrongSchema(config); 12 | } 13 | 14 | /** 15 | * The configuration for a strong GraphQL schema. 16 | */ 17 | export interface StrongSchemaConfig { 18 | readonly query: StrongObjectType; 19 | readonly mutation?: StrongObjectType; 20 | 21 | /** 22 | * Runs only once at the beginning of an execution for this schema. 23 | */ 24 | onExecute?(value: TValue, context: TContext, info: GraphQLResolveInfo): void | Promise; 25 | } 26 | 27 | /** 28 | * The strong GraphQL schema represents a type-safe GraphQL schema that forces 29 | * type correctness on execution with the `execute` method. 30 | */ 31 | export 32 | class StrongSchema 33 | extends GraphQLSchema { 34 | constructor(config: StrongSchemaConfig) { 35 | super({ 36 | query: config.query.ofType.clone(), 37 | mutation: config.mutation && config.mutation.ofType.clone(), 38 | }); 39 | 40 | const { onExecute } = config; 41 | 42 | // If we were given an `onExecute` function in the configuration then we 43 | // must add it to our root query types. 44 | if (onExecute) { 45 | const executedOperations = new WeakMap>(); 46 | 47 | const rootTypes = [ 48 | this.getQueryType(), 49 | this.getMutationType(), 50 | ].filter(Boolean); 51 | 52 | rootTypes.forEach(rootType => { 53 | const fields = rootType.getFields(); 54 | 55 | for (const fieldName of Object.keys(fields)) { 56 | const resolver = fields[fieldName].resolve || defaultFieldResolver; 57 | 58 | // Wrap our resolver so that our `onExecute` handler runs if provided. 59 | fields[fieldName].resolve = async(source, args, context, info) => { 60 | // If we have not yet executed our root level `onExecute` function, 61 | // then call it. 62 | if (!executedOperations.has(info.operation)) { 63 | executedOperations.set(info.operation, Promise.resolve(onExecute(source, context, info))); 64 | } 65 | // Wait for our `onExecute` function to resolve or reject whether 66 | // `onExecute` was called here or not. 67 | await executedOperations.get(info.operation); 68 | 69 | return await resolver(source, args, context, info); 70 | }; 71 | } 72 | }); 73 | } 74 | } 75 | 76 | /** 77 | * A type-safe execution function for our strong GraphQL schema. With this 78 | * function you will be forced to provide values and contexts of the correct 79 | * types. 80 | */ 81 | public execute(query: string, value: TValue, context: TContext, variables: { [key: string]: any } = {}, operation?: string): Promise { 82 | return graphql(this, query, value, context, variables, operation); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLType, GraphQLInputType, GraphQLOutputType } from 'graphql'; 2 | 3 | /** 4 | * A strong GraphQL type which when composed with other strong GraphQL types 5 | * makes a system that is language type-safe. 6 | * 7 | * Most of the time, types will also inherit from `StrongInputType` or 8 | * `StrongOutputType`. 9 | */ 10 | export interface StrongType { 11 | /** 12 | * Serves as a flag to simulate nominal types. Otherwise a `StrongType` 13 | * would be equal to any other type given TypeScript’s structural approach to 14 | * typing. 15 | * 16 | * This is a private field and may be changed or removed at any time. 17 | */ 18 | readonly _strongType: true; 19 | 20 | /** 21 | * Serves as a flag in the type system so that two different 22 | * `StrongType`’s with different `TValue`s won’t be considered as the 23 | * same thing by TypeScript’s structural type system. 24 | * 25 | * This value will always be null, it is useless outside of the type system, 26 | * private, and may be changed or removed at any time. 27 | */ 28 | readonly _strongValue: TValue; 29 | 30 | /** 31 | * Gets the vanilla GraphQL-JS type for this strong type. Many types will 32 | * just return themselves as they extend a GraphQL type. 33 | */ 34 | getWeakType(): GraphQLType; 35 | 36 | /** 37 | * Gets a nullable variation of this type since types are non-null by default. 38 | * If this type is already nullable, the type may return itself. 39 | */ 40 | nullable(): StrongType; 41 | } 42 | 43 | /** 44 | * A GraphQL input type. Input types may be used anywhere in GraphQL to receive 45 | * data. 46 | */ 47 | export interface StrongInputType extends StrongType { 48 | /** 49 | * A flag that marks these objects as an input type. We never use this outside 50 | * of the type system. It is private and may be removed at any time. 51 | */ 52 | readonly _strongInputType: true; 53 | 54 | /** 55 | * Gets the vanilla GraphQL-JS input type for this strong type. Many types 56 | * will just return themselves as they extend a GraphQL type. 57 | */ 58 | getWeakInputType(): GraphQLInputType; 59 | 60 | /** 61 | * Gets a nullable variation of this type since types are non-null by default. 62 | * If this type is already nullable, the type may return itself. 63 | */ 64 | nullable(): StrongInputType; 65 | } 66 | 67 | /** 68 | * A GraphQL output type. Output types are used whenever you are outputting data 69 | * from the API. 70 | */ 71 | export interface StrongOutputType extends StrongType { 72 | /** 73 | * A flag that marks these objects as an input type. We never use this outside 74 | * of the type system. It is private and may be removed at any time. 75 | */ 76 | readonly _strongOutputType: true; 77 | 78 | /** 79 | * Gets the vanilla GraphQL-JS output type for this strong type. Many types 80 | * will just return themselves as they extend a GraphQL type. 81 | */ 82 | getWeakOutputType(): GraphQLOutputType; 83 | 84 | /** 85 | * Gets a nullable variation of this type since types are non-null by default. 86 | * If this type is already nullable, the type may return itself. 87 | */ 88 | nullable(): StrongOutputType; 89 | } 90 | 91 | /** 92 | * A convenience interface that merges both `StrongInputType` and 93 | * `StrongOutputType`. 94 | */ 95 | export interface StrongInputOutputType extends StrongType, StrongInputType, StrongOutputType { 96 | /** 97 | * Gets a nullable variation of this type since types are non-null by default. 98 | * If this type is already nullable, the type may return itself. 99 | */ 100 | nullable(): StrongInputOutputType; 101 | } 102 | -------------------------------------------------------------------------------- /src/wrap.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLType, GraphQLInputType, GraphQLOutputType, GraphQLNonNull, GraphQLInt, GraphQLFloat, GraphQLString, GraphQLBoolean, GraphQLID, assertInputType, assertOutputType } from 'graphql'; 2 | import { StrongInputType, StrongOutputType, StrongInputOutputType } from './type'; 3 | 4 | // Wrappings for types that are baked into the standard library. 5 | export const IntegerType = wrapWeakType(GraphQLInt); 6 | export const FloatType = wrapWeakType(GraphQLFloat); 7 | export const StringType = wrapWeakType(GraphQLString); 8 | export const BooleanType = wrapWeakType(GraphQLBoolean); 9 | export const IDType = wrapWeakType(GraphQLID); 10 | 11 | /** 12 | * Wraps a `GraphQLType` into a strong GraphQL type with an associated type in 13 | * the type system. If the `GraphQLType` is a `GraphQLInputType` a 14 | * `StrongInputType` will be returned, if the `GraphQLType` is a 15 | * `GraphQLOutputType` a `StrongOutputType` will be returned, and if the 16 | * `GraphQLType` is both a `GraphQLInputType` and a `GraphQLOutputType` a 17 | * `StrongInputOutputType` will be returned. 18 | */ 19 | export function wrapWeakType(type: GraphQLInputType & GraphQLOutputType): StrongInputOutputType; 20 | export function wrapWeakType(type: GraphQLInputType): StrongInputType; 21 | export function wrapWeakType(type: GraphQLOutputType): StrongOutputType; 22 | export function wrapWeakType(type: GraphQLType): StrongInputOutputType { 23 | const nullableStrongType: StrongInputOutputType = { 24 | _strongType: true, 25 | _strongInputType: true, 26 | _strongOutputType: true, 27 | _strongValue: undefined as any, 28 | getWeakType: () => type, 29 | getWeakInputType: () => assertInputType(type), 30 | getWeakOutputType: () => assertOutputType(type), 31 | nullable: () => nullableStrongType, 32 | }; 33 | const strongType: StrongInputOutputType = { 34 | _strongType: true, 35 | _strongInputType: true, 36 | _strongOutputType: true, 37 | _strongValue: undefined as any, 38 | getWeakType: () => new GraphQLNonNull(type), 39 | getWeakInputType: () => new GraphQLNonNull(assertInputType(type)), 40 | getWeakOutputType: () => new GraphQLNonNull(assertOutputType(type)), 41 | nullable: () => nullableStrongType, 42 | }; 43 | return strongType; 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "rootDir": "src", 5 | "target": "es6", 6 | "module": "commonjs", 7 | "alwaysStrict": true, 8 | "newLine": "LF", 9 | "declaration": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitAny": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noUnusedLocals": true, 16 | "strictNullChecks": true 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | "**/__tests__/*.ts", 21 | "**/__tests__/*/*.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "adjacent-overload-signatures": true, 4 | "member-access": true, 5 | "member-ordering": [true, { 6 | "order": [ 7 | "public-static-field", 8 | "protected-static-field", 9 | "private-static-field", 10 | "public-static-method", 11 | "protected-static-method", 12 | "private-static-method", 13 | "constructor" 14 | ] 15 | }], 16 | "no-any": false, 17 | "no-inferrable-types": [true, "ignore-params"], 18 | "no-internal-module": true, 19 | "no-namespace": true, 20 | "no-reference": true, 21 | "no-var-requires": true, 22 | "only-arrow-functions": [true, "allow-declarations"], 23 | "prefer-for-of": true, 24 | "typedef": [true, "call-signature", "property-declaration", "member-variable-declaration"], 25 | "typedef-whitespace": [true, { 26 | "call-signature": "nospace", 27 | "index-signature": "nospace", 28 | "parameter": "nospace", 29 | "property-declaration": "nospace", 30 | "variable-delcaration": "onespace" 31 | }], 32 | 33 | "ban": false, 34 | "curly": true, 35 | "forin": true, 36 | "label-position": true, 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-conditional-assignment": true, 40 | "no-console": [true, "log", "error", "warn", "info", "trace"], 41 | "no-construct": true, 42 | "no-debugger": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": true, 45 | "no-eval": true, 46 | "no-for-in-array": false, 47 | "no-invalid-this": true, 48 | "no-null-keyword": false, 49 | "no-shadowed-variable": true, 50 | "no-unsafe-finally": true, 51 | "no-unused-expression": true, 52 | "no-unused-new": true, 53 | "no-use-before-declare": true, 54 | "no-var-keyword": true, 55 | "radix": true, 56 | "restrict-plus-operands": false, 57 | "switch-default": true, 58 | "triple-equals": [true, "allow-null-check"], 59 | "use-isnan": true, 60 | 61 | "cyclomatic-complexity": [false], 62 | "eofline": true, 63 | "indent": [true, "spaces"], 64 | "linebreak-style": [true, "LF"], 65 | "max-classes-per-file": [false], 66 | "max-file-line-count": [false], 67 | "max-line-length": [false], 68 | "no-default-export": true, 69 | "no-mergeable-namespace": true, 70 | "no-require-imports": true, 71 | "no-trailing-whitespace": true, 72 | "object-literal-sort-keys": false, 73 | "trailing-comma": [true, { "multiline": "always", "singleline": "never" }], 74 | 75 | "align": [true, "parameters", "statements"], 76 | "array-type": [true, "generic"], 77 | "arrow-parens": false, 78 | "class-name": true, 79 | "comment-format": [true, "check-space"], 80 | "completed-docs": [false], 81 | "file-header": [false], 82 | "interface-name": [false], 83 | "jsdoc-format": true, 84 | "new-parens": true, 85 | "no-angle-bracket-type-assertion": true, 86 | "no-consecutive-blank-lines": [true, 2], 87 | "no-parameter-properties": true, 88 | "object-literal-key-quotes": [true, "as-needed"], 89 | "object-literal-shorthand": true, 90 | "one-line": [true, "check-open-brace", "check-whitespace"], 91 | "one-variable-per-declaration": [true], 92 | "ordered-imports": [false], 93 | "quotemark": [true, "single"], 94 | "semicolon": [true, "always"], 95 | "space-before-function-paren": [true, "never"], 96 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], 97 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-type", "check-typecast"] 98 | } 99 | } 100 | --------------------------------------------------------------------------------