├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── SuperTestGraphQL.ts ├── SuperTestWSGraphQL.ts ├── index.ts ├── supertest-graphql.test.ts ├── types.ts └── utils.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "rules": { 15 | "prettier/prettier": "error" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [12.x, 14.x, 16.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - run: npm ci 29 | - run: npm run check-types 30 | - run: npm run lint 31 | - run: npm test 32 | shipit: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: actions/setup-node@v2 37 | with: 38 | node-version: ${{ matrix.node-version }} 39 | cache: 'npm' 40 | - name: Prepare repository 41 | run: git fetch --unshallow --tags 42 | - run: npm ci 43 | - run: npm run release 44 | env: 45 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Tests", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeArgs": [ 12 | "--inspect-brk", 13 | "${workspaceRoot}/node_modules/.bin/jest", 14 | "--runInBand" 15 | ], 16 | "console": "integratedTerminal", 17 | "internalConsoleOptions": "neverOpen", 18 | "port": 9229 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.1.4 (Thu Apr 21 2022) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Bump minimist from 1.2.5 to 1.2.6 [#9](https://github.com/alexstrat/supertest-graphql/pull/9) ([@dependabot[bot]](https://github.com/dependabot[bot])) 6 | 7 | #### Authors: 1 8 | 9 | - [@dependabot[bot]](https://github.com/dependabot[bot]) 10 | 11 | --- 12 | 13 | # v1.1.3 (Tue Mar 22 2022) 14 | 15 | #### 🐛 Bug Fix 16 | 17 | - Add supports for subscriptions [#6](https://github.com/alexstrat/supertest-graphql/pull/6) ([@alexstrat](https://github.com/alexstrat)) 18 | 19 | #### Authors: 1 20 | 21 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat)) 22 | 23 | --- 24 | 25 | # v1.1.2 (Thu Dec 23 2021) 26 | 27 | #### 🐛 Bug Fix 28 | 29 | - Remove self from deps [#4](https://github.com/alexstrat/supertest-graphql/pull/4) ([@alexstrat](https://github.com/alexstrat)) 30 | 31 | #### Authors: 1 32 | 33 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat)) 34 | 35 | --- 36 | 37 | # v1.1.1 (Sat Dec 18 2021) 38 | 39 | #### ⚠️ Pushed to `master` 40 | 41 | - Add auto badge ([@alexstrat](https://github.com/alexstrat)) 42 | 43 | #### Authors: 1 44 | 45 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat)) 46 | 47 | --- 48 | 49 | # v1.1.0 (Sat Dec 18 2021) 50 | 51 | #### 🚀 Enhancement 52 | 53 | - Remove usage of `any` in typing [#3](https://github.com/alexstrat/supertest-graphql/pull/3) ([@alexstrat](https://github.com/alexstrat)) 54 | 55 | #### ⚠️ Pushed to `master` 56 | 57 | - add fetch tags ([@alexstrat](https://github.com/alexstrat)) 58 | 59 | #### Authors: 1 60 | 61 | - Alexandre Lacheze ([@alexstrat](https://github.com/alexstrat)) 62 | 63 | --- 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # supertest-graphql 2 |

3 | npm version 4 | 5 | License: ISC 6 | 7 | Auto Release 8 |

9 | 10 | > Extends [supertest](https://www.npmjs.com/package/supertest) to test a GraphQL endpoint 11 | 12 | ## Install 13 | 14 | ```sh 15 | npm install supertest-graphql 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```ts 21 | import request from 'supertest-graphql' 22 | import gql from 'graphql-tag' 23 | 24 | test('should get pets', async () => { 25 | const { data } = await request(app) 26 | .query(gql` 27 | query { 28 | pets { 29 | name 30 | petType 31 | } 32 | } 33 | `) 34 | .expectNoErrors() 35 | 36 | expect(data.pets).toHaveLength(2) 37 | }) 38 | ``` 39 | 40 | ### Set expectations 41 | `expectNoErrors` will verify that the API response has no `errors` in 42 | its result payload. 43 | 44 | ```ts 45 | await request(app) 46 | .query('blooop') 47 | .expectNoErrors() 48 | // expected no errors but got 1 error(s) in GraphQL response: Syntax Error: Unexpected Name "blooop". 49 | ``` 50 | ### Variables 51 | ```ts 52 | const { data } = await request(app) 53 | .query(gql` 54 | query GetPets($first: Int){ 55 | pets(first: $first) { 56 | name 57 | petType 58 | } 59 | } 60 | `) 61 | .variables({ first: 4 }) 62 | ``` 63 | 64 | ### Mutation 65 | ```ts 66 | const { data } = await request(app) 67 | .mutate(gql` 68 | mutation PetAnimal($petId: ID!) { 69 | petAnimal(petId: $petId) { 70 | name 71 | petType 72 | } 73 | } 74 | `) 75 | .variables({petId: 'my-cat' }) 76 | ``` 77 | 78 | ### Subscriptions with WebScoket 79 | ```ts 80 | import { supertestWs } from 'supertest-graphql' 81 | import gql from 'graphql-tag' 82 | 83 | // for websocket the server needs to be started and stopped manually 84 | beForeEach(() => server.listen(0, "localhost")) 85 | afterEach(() => server.close()) 86 | 87 | test('should get pets', async () => { 88 | const sub = await supertestWs(app) 89 | .subscribe(gql` 90 | subscription { 91 | newPetAdded { 92 | name 93 | petType 94 | } 95 | } 96 | `) 97 | 98 | // will wait or pop the next value 99 | const { data } = await sub.next().expectNoErrors() 100 | 101 | expect(data.newPetAdded.name).toEqual('Fifi') 102 | }) 103 | ``` 104 | 105 | ### Authentication 106 | ```ts 107 | const { data } = await request(app) 108 | .auth('username', 'password') 109 | .query(...) 110 | ``` 111 | 112 | or via headers: 113 | ```ts 114 | const { data } = await request(app) 115 | .set('authorization', 'my token') 116 | .query(...) 117 | ``` 118 | 119 | For WebSocket with `connectionParams`: 120 | ```ts 121 | import { supertestWs } from 'supertest-graphql' 122 | 123 | const sub = await supertestWs(app) 124 | .connectionParams({ 125 | token: 'my token' 126 | }) 127 | .subscribe(...) 128 | ``` 129 | ### Change GraphQL endpoint path 130 | 131 | By default, the execution are sent to `/graphql`. 132 | 133 | You can change this with `.path()`: 134 | 135 | ```ts 136 | const { data } = await request(app) 137 | .path('/new-graphql') 138 | .query(...) 139 | ``` 140 | 141 | ### Use WebSocket legacy protocol 142 | 143 | ```ts 144 | import { supertestWs, LEGACY_WEBSOCKET_PROTOCOL } from 'supertest-graphql' 145 | 146 | const sub = await supertestWs(app) 147 | .protocol(LEGACY_WEBSOCKET_PROTOCOL) 148 | .subscribe(...) 149 | ``` -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supertest-graphql", 3 | "version": "1.1.4", 4 | "description": "Extends supertest to test a GraphQL endpoint", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "rm -rf ./dist/* && tsc", 9 | "test": "jest", 10 | "check-types": "tsc --noEmit", 11 | "lint": "eslint . --ext .ts", 12 | "prepublishOnly": "npm run build", 13 | "release": "auto shipit" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/alexstrat/supertest-graphql" 21 | }, 22 | "keywords": [ 23 | "supertest", 24 | "graphql", 25 | "test", 26 | "jest", 27 | "apollo" 28 | ], 29 | "author": "Alexandre Lacheze", 30 | "license": "ISC", 31 | "peerDependencies": { 32 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 33 | }, 34 | "devDependencies": { 35 | "@graphql-tools/schema": "^8.3.1", 36 | "@types/express": "^4.17.13", 37 | "@types/jest": "^27.0.3", 38 | "@types/ws": "^8.2.2", 39 | "@typescript-eslint/eslint-plugin": "^5.7.0", 40 | "@typescript-eslint/parser": "^5.7.0", 41 | "apollo-server-express": "^3.5.0", 42 | "auto": "^10.32.5", 43 | "eslint": "^8.4.1", 44 | "eslint-config-prettier": "^8.3.0", 45 | "eslint-plugin-prettier": "^4.0.0", 46 | "express": "^4.17.2", 47 | "graphql": "^16.1.0", 48 | "graphql-subscriptions": "^2.0.0", 49 | "jest": "^27.4.5", 50 | "prettier": "2.5.1", 51 | "ts-jest": "^27.1.2", 52 | "ts-node": "^10.4.0", 53 | "typescript": "^4.5.4" 54 | }, 55 | "dependencies": { 56 | "delay": "^5.0.0", 57 | "@types/supertest": "^2.0.11", 58 | "graphql-ws": "^5.6.3", 59 | "subscriptions-transport-ws": "^0.11.0", 60 | "supertest": "^6.1.6", 61 | "ws": "^8.4.0", 62 | "zen-observable-ts": "^1.1.0" 63 | }, 64 | "auto": { 65 | "plugins": [ 66 | "npm" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/SuperTestGraphQL.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, ExecutionResult, GraphQLError, print } from "graphql"; 2 | import { SuperAgentTest, Response } from "supertest"; 3 | import { AssertFn, Variables } from "./types"; 4 | 5 | import { asserNoError, getOperationName, wrapAssertFn } from "./utils"; 6 | 7 | export type SuperTestExecutionResult = ExecutionResult & { 8 | response: Response; 9 | }; 10 | 11 | export default class SuperTestGraphQL 12 | implements PromiseLike> 13 | { 14 | private _query?: string; 15 | private _operationName?: string; 16 | private _variables?: TVariables; 17 | private _path = "/graphql"; 18 | private _asserts: AssertFn[] = []; 19 | 20 | constructor(private _supertest: SuperAgentTest) {} 21 | 22 | /** 23 | * Send a GraphQL Query Document to the GraphQL server for execution. 24 | * @param query - the query to execute as string or `DocumentNode` 25 | * @param variables - the variables for this query 26 | */ 27 | query(query: DocumentNode | string, variables?: TVariables): this { 28 | return this.operation(query, variables); 29 | } 30 | 31 | /** 32 | * Send a GraphQL Query Document to the GraphQL server for execution. 33 | * @param mutation - the mutation to execute as string or `DocumentNode` 34 | * @param variables - the variables for this mutation 35 | */ 36 | mutate(mutation: DocumentNode | string, variables?: TVariables): this { 37 | return this.operation(mutation, variables); 38 | } 39 | 40 | /** 41 | * Send a GraphQL Query Document to the GraphQL server for execution. 42 | * @param operation - the operation to execute as string or `DocumentNode` 43 | * @param variables - the variables for this operation 44 | */ 45 | operation(operation: DocumentNode | string, variables?: TVariables): this { 46 | if (typeof operation !== "string") { 47 | this._operationName = getOperationName(operation); 48 | } 49 | this._query = typeof operation === "string" ? operation : print(operation); 50 | this._variables = variables; 51 | return this; 52 | } 53 | 54 | /** 55 | * Set variables. 56 | * @param - variables 57 | */ 58 | variables(variables: TVariables): this { 59 | this._variables = variables; 60 | return this; 61 | } 62 | 63 | /** 64 | * Set the GraphQL endpoint path. 65 | * 66 | * @default "/graphql" 67 | */ 68 | path(path: string): this { 69 | this._path = path; 70 | return this; 71 | } 72 | 73 | /** 74 | * Set authentication parameters for the request. 75 | * 76 | * @see [supragent.auth](https://visionmedia.github.io/superagent/#authentication) 77 | */ 78 | auth(user: string, pass: string, options?: { type: "basic" | "auto" }): this; 79 | auth(token: string, options: { type: "bearer" }): this; 80 | auth( 81 | ...args: 82 | | [string, string] 83 | | [string, string, { type: "basic" | "auto" }?] 84 | | [string, { type: "bearer" }] 85 | ): this { 86 | this._supertest.auth(...(args as Parameters)); 87 | return this; 88 | } 89 | 90 | /** 91 | * Set headers for the request. 92 | * 93 | * @see [supragent.set](https://visionmedia.github.io/superagent/#setting-header-fields) 94 | */ 95 | // can't use Parameters<> because not supported with several overloads 96 | // https://github.com/microsoft/TypeScript/issues/26591 97 | set(field: object): this; 98 | set(field: string, val: string): this; 99 | set(field: "Cookie", val: string[]): this; 100 | set(...args: [object] | [string, string] | ["Cookie", string[]]): this { 101 | this._supertest.set(...(args as Parameters)); 102 | return this; 103 | } 104 | 105 | /** 106 | * Assert that there is no errors (`.errors` field) in response returned from the GraphQL API. 107 | */ 108 | expectNoErrors(): this { 109 | this._asserts.push(wrapAssertFn(asserNoError)); 110 | return this; 111 | } 112 | 113 | /** 114 | * Access to underlying supertest instance. 115 | */ 116 | supertest(): SuperAgentTest { 117 | return this._supertest; 118 | } 119 | 120 | private async assert(result: SuperTestExecutionResult): Promise { 121 | for (const assertFn of this._asserts) { 122 | const maybeError = await assertFn(result); 123 | if (maybeError instanceof Error) throw maybeError; 124 | } 125 | } 126 | 127 | async end(): Promise> { 128 | if (this._query === undefined) 129 | throw new Error("You should call `query` or `mutate`"); 130 | 131 | const payload: RequestPayload = { 132 | query: this._query, 133 | }; 134 | if (this._operationName) { 135 | payload.operationName = this._operationName; 136 | } 137 | if (this._variables) { 138 | payload.variables = this._variables; 139 | } 140 | 141 | const response = await this._supertest 142 | .post(this._path) 143 | .accept("application/json") 144 | .send(payload); 145 | 146 | if (typeof response.body !== "object") { 147 | throw new Error(`Received a non valid body ${response.body}`); 148 | } 149 | const result = { ...response.body, response }; 150 | 151 | await this.assert(result); 152 | 153 | return { ...response.body, response }; 154 | } 155 | 156 | async then, TResult2 = never>( 157 | onfulfilled?: 158 | | (( 159 | value: SuperTestExecutionResult 160 | ) => TResult1 | PromiseLike) 161 | | null, 162 | onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null 163 | ): Promise { 164 | try { 165 | if (this._query === undefined) 166 | throw new Error("You should call `query` or `mutate`"); 167 | const res = await this.end(); 168 | if (onfulfilled) return onfulfilled(res); 169 | // @ts-expect-error no idea why 170 | return res; 171 | } catch (e) { 172 | if (onrejected) return onrejected(e); 173 | throw new Error("No rejection"); 174 | } 175 | } 176 | } 177 | 178 | type RequestPayload = { 179 | query: string; 180 | operationName?: string; 181 | variables?: TVariables; 182 | }; 183 | -------------------------------------------------------------------------------- /src/SuperTestWSGraphQL.ts: -------------------------------------------------------------------------------- 1 | import delay from "delay"; 2 | import { DocumentNode, ExecutionResult, print } from "graphql"; 3 | import { Client, createClient, Disposable, Sink } from "graphql-ws"; 4 | import { ObjMap } from "graphql/jsutils/ObjMap"; 5 | import { SubscriptionClient } from "subscriptions-transport-ws"; 6 | import ws from "ws"; 7 | import { Subscriber } from "zen-observable-ts"; 8 | 9 | import { AssertFn, Variables } from "./types"; 10 | import { 11 | asserNoError, 12 | BlockingQueue, 13 | getOperationName, 14 | wrapAssertFn, 15 | } from "./utils"; 16 | 17 | // /!\ graphql-ws is the legacy one 18 | export type WebSocketProtocol = "graphql-transport-ws" | "graphql-ws"; 19 | 20 | /** 21 | * The protocol implemented by the library `subscriptions-transport-ws` and 22 | * that is now considered legacy. 23 | */ 24 | export const LEGACY_WEBSOCKET_PROTOCOL = "graphql-ws"; 25 | 26 | export class SuperTestExecutionNextResult 27 | implements PromiseLike> 28 | { 29 | private _asserts: AssertFn[] = []; 30 | 31 | constructor(private pop: Promise>) {} 32 | async then< 33 | TResult1 = ExecutionResult>, 34 | TResult2 = never 35 | >( 36 | onfulfilled?: 37 | | (( 38 | value: ExecutionResult> 39 | ) => TResult1 | PromiseLike) 40 | | null, 41 | onrejected?: ((reason: any) => TResult2 | PromiseLike) | null 42 | ): Promise { 43 | const res = await this.pop; 44 | await this.assert(res); 45 | if (onfulfilled) return onfulfilled(res); 46 | // @ts-expect-error no idea why 47 | return res; 48 | } 49 | 50 | /** 51 | * Assert that there is no errors (`.errors` field) in response returned from the GraphQL API. 52 | */ 53 | expectNoErrors(): this { 54 | this._asserts.push(wrapAssertFn(asserNoError)); 55 | return this; 56 | } 57 | 58 | private async assert(result: ExecutionResult): Promise { 59 | for (const assertFn of this._asserts) { 60 | const maybeError = await assertFn(result); 61 | if (maybeError instanceof Error) throw maybeError; 62 | } 63 | } 64 | } 65 | 66 | export class SuperTestExecutionStreamingResult { 67 | private queue: BlockingQueue> = new BlockingQueue(); 68 | 69 | constructor( 70 | private client: Disposable, 71 | subscriber: Subscriber> 72 | ) { 73 | subscriber({ 74 | next: (res) => this.queue.push(res), 75 | complete: () => { 76 | // do something 77 | }, 78 | error: () => { 79 | // do something 80 | }, 81 | closed: false, 82 | }); 83 | } 84 | 85 | /** 86 | * Get the next result that the operation is emitting. 87 | */ 88 | next(): SuperTestExecutionNextResult { 89 | return new SuperTestExecutionNextResult(this.queue.pop()); 90 | } 91 | 92 | /** 93 | * Flush the pending results from the queue. 94 | */ 95 | flush(): ExecutionResult[] { 96 | return this.queue.flush(); 97 | } 98 | 99 | /** 100 | * Assert that no more results are pending. 101 | */ 102 | expectNoPending(): this { 103 | if (this.queue.length > 0) { 104 | throw new Error(`expect no pending, but got ${this.queue.length}`); 105 | } 106 | return this; 107 | } 108 | 109 | /** 110 | * Close the operation and the connection. 111 | */ 112 | async close(): Promise { 113 | await this.client.dispose(); 114 | } 115 | } 116 | 117 | export class SuperTestExecutionStreamingResultPool { 118 | private _subscriptions: SuperTestExecutionStreamingResult[] = []; 119 | async endAll(): Promise { 120 | await Promise.all(this._subscriptions.map((c) => c.close())); 121 | this._subscriptions = []; 122 | } 123 | add(sub: SuperTestExecutionStreamingResult): void { 124 | this._subscriptions.push(sub); 125 | } 126 | } 127 | 128 | type ConnectionParams = { 129 | [paramName: string]: any; 130 | }; 131 | export default class SuperTestWSGraphQL 132 | implements PromiseLike> 133 | { 134 | private _query?: string; 135 | private _operationName?: string; 136 | private _variables?: TVariables; 137 | private _path = "/graphql"; 138 | private _protocol: WebSocketProtocol = "graphql-transport-ws"; 139 | private _connectionParams?: ConnectionParams; 140 | 141 | constructor( 142 | private _hostname: string, 143 | private _pool?: SuperTestExecutionStreamingResultPool 144 | ) {} 145 | 146 | /** 147 | * Send a GraphQL Query Document to the GraphQL server for execution. 148 | * @param operation - the query to execute as string or `DocumentNode` 149 | * @param variables - the variables for this query 150 | */ 151 | subscribe(operation: DocumentNode | string, variables?: TVariables): this { 152 | this.operation(operation, variables); 153 | return this; 154 | } 155 | 156 | /** 157 | * Send a GraphQL Query Document to the GraphQL server for execution. 158 | * @param query - the query to execute as string or `DocumentNode` 159 | * @param variables - the variables for this query 160 | */ 161 | query(query: DocumentNode | string, variables?: TVariables): this { 162 | return this.operation(query, variables); 163 | } 164 | 165 | /** 166 | * Send a GraphQL Query Document to the GraphQL server for execution. 167 | * @param mutation - the mutation to execute as string or `DocumentNode` 168 | * @param variables - the variables for this mutation 169 | */ 170 | mutate(mutation: DocumentNode | string, variables?: TVariables): this { 171 | return this.operation(mutation, variables); 172 | } 173 | 174 | /** 175 | * Send a GraphQL Query Document to the GraphQL server for execution. 176 | * @param operation - the operation to execute as string or `DocumentNode` 177 | * @param variables - the variables for this operation 178 | */ 179 | operation(operation: DocumentNode | string, variables?: TVariables): this { 180 | if (typeof operation !== "string") { 181 | this._operationName = getOperationName(operation); 182 | } 183 | this._query = typeof operation === "string" ? operation : print(operation); 184 | this._variables = variables; 185 | return this; 186 | } 187 | 188 | /** 189 | * Set variables. 190 | * @param - variables 191 | */ 192 | variables(variables: TVariables): this { 193 | this._variables = variables; 194 | return this; 195 | } 196 | 197 | /** 198 | * Set the GraphQL endpoint path. 199 | * 200 | * @default "/graphql" 201 | */ 202 | path(path: string): this { 203 | this._path = path; 204 | return this; 205 | } 206 | 207 | /** 208 | * Set the GraphQL WebSocket porotocol. 209 | * You can set the legacy protocol with the variable `LEGACY_WEBSOCKET_PROTOCOL`. 210 | */ 211 | protocol(wsProtocol: WebSocketProtocol): this { 212 | this._protocol = wsProtocol; 213 | return this; 214 | } 215 | 216 | /** 217 | * Set connection params. 218 | */ 219 | connectionParams(params: ConnectionParams): this { 220 | this._connectionParams = params; 221 | return this; 222 | } 223 | 224 | async then< 225 | TResult1 = SuperTestExecutionStreamingResult, 226 | TResult2 = never 227 | >( 228 | onfulfilled?: 229 | | (( 230 | value: SuperTestExecutionStreamingResult 231 | ) => TResult1 | PromiseLike) 232 | | null, 233 | onrejected?: ((reason: any) => TResult2 | PromiseLike) | null 234 | ): Promise { 235 | try { 236 | const url = new URL(this._path, this._hostname).toString(); 237 | const connect = 238 | this._protocol === "graphql-ws" 239 | ? connectAndAdaptLegacyClient 240 | : connectClient; 241 | 242 | const client = await connect(url, this._connectionParams); 243 | 244 | if (!this._query) throw new Error("Missing a query"); 245 | const query = this._query; 246 | const streamingResult = new SuperTestExecutionStreamingResult( 247 | client, 248 | (observer) => 249 | client.subscribe( 250 | { 251 | query, 252 | variables: this._variables, 253 | operationName: this._operationName, 254 | }, 255 | observer 256 | ) 257 | ); 258 | 259 | this._pool?.add(streamingResult); 260 | 261 | if (onfulfilled) return onfulfilled(streamingResult); 262 | // @ts-expect-error no idea why 263 | return streamingResult; 264 | } catch (e) { 265 | if (onrejected) return onrejected(e); 266 | throw new Error("No rejection"); 267 | } 268 | } 269 | } 270 | 271 | type SubscriptionArgs = { 272 | query: string; 273 | variables?: TVariables; 274 | operationName?: string; 275 | }; 276 | 277 | type AbstractClient = { 278 | dispose: () => void | Promise; 279 | subscribe: ( 280 | args: SubscriptionArgs, 281 | observer: Sink> 282 | ) => () => void; 283 | }; 284 | 285 | const connectClient = async ( 286 | url: string, 287 | connectionParams?: ConnectionParams 288 | ): Promise => { 289 | return await new Promise((res, reject) => { 290 | const client = createClient({ 291 | url, 292 | connectionParams, 293 | lazy: false, 294 | onNonLazyError: (error) => reject(error), 295 | webSocketImpl: ws, 296 | }); 297 | client.on("connected", () => res(client)); 298 | }); 299 | }; 300 | 301 | const connectAndAdaptLegacyClient = async ( 302 | url: string, 303 | connectionParams?: ConnectionParams 304 | ): Promise => { 305 | const legacyClient = await new Promise((res, reject) => { 306 | const client = new SubscriptionClient( 307 | url, 308 | { 309 | connectionParams, 310 | connectionCallback: (error) => { 311 | if (error) { 312 | client.close(); 313 | return reject(error); 314 | } 315 | res(client); 316 | }, 317 | }, 318 | ws 319 | ); 320 | }); 321 | 322 | return { 323 | dispose: () => { 324 | legacyClient.close(); 325 | }, 326 | subscribe: (args, observer) => { 327 | // @ts-expect-error not exact but fine 328 | const { unsubscribe } = legacyClient.request(args).subscribe(observer); 329 | return unsubscribe; 330 | }, 331 | }; 332 | }; 333 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | import * as https from "https"; 3 | import { agent } from "supertest"; 4 | import SuperTestGraphQL from "./SuperTestGraphQL"; 5 | import SuperTestWSGraphQL, { 6 | SuperTestExecutionStreamingResultPool, 7 | } from "./SuperTestWSGraphQL"; 8 | import { Variables } from "./types"; 9 | 10 | /** 11 | * Test against the given `app` returnig a new `SuperTestGraphQL`. 12 | */ 13 | const supertest = ( 14 | app: unknown 15 | ): SuperTestGraphQL => { 16 | const supertest = agent(app); 17 | return new SuperTestGraphQL(supertest); 18 | }; 19 | 20 | // todo: put me somewhere 21 | function getWSBase(server: https.Server | http.Server) { 22 | if (typeof server === "string") { 23 | return server; 24 | } 25 | 26 | const address = server.address(); 27 | if (!address) { 28 | // see https://github.com/visionmedia/supertest/issues/566 29 | throw new Error( 30 | "Server must be listening:\n" + 31 | "beforeEach((done) => server.listen(0, 'localhost', done));\n" + 32 | "afterEach((done) => server.close(done));\n" + 33 | "\n" + 34 | "supertest's request(app) syntax is not supported (find out more: https://github.com/davidje13/superwstest#why-isnt-requestapp-supported)" 35 | ); 36 | } 37 | 38 | const protocol = server instanceof https.Server ? "wss" : "ws"; 39 | let hostname; 40 | if (typeof address === "object") { 41 | if (address.family.toLowerCase() === "ipv6") { 42 | hostname = `[${address.address}]`; 43 | } else { 44 | hostname = address.address; 45 | } 46 | } else { 47 | hostname = address; 48 | } 49 | // @ts-expect-error fix me 50 | return `${protocol}://${hostname}:${address.port}`; 51 | } 52 | 53 | const mainPool = new SuperTestExecutionStreamingResultPool(); 54 | 55 | export const supertestWs = Object.assign( 56 | ( 57 | // todo: accept string 58 | app: https.Server | http.Server | string 59 | ): SuperTestWSGraphQL => { 60 | // todo: prametrize 61 | const base = typeof app === "string" ? app : getWSBase(app); 62 | return new SuperTestWSGraphQL(base, mainPool); 63 | }, 64 | { 65 | end: () => mainPool.endAll(), 66 | } 67 | ); 68 | 69 | export * from "./SuperTestGraphQL"; 70 | export * from "./SuperTestWSGraphQL"; 71 | export * from "./types"; 72 | export { SuperTestGraphQL }; 73 | export default supertest; 74 | -------------------------------------------------------------------------------- /src/supertest-graphql.test.ts: -------------------------------------------------------------------------------- 1 | import { gql, ApolloServer, ExpressContext } from "apollo-server-express"; 2 | import express from "express"; 3 | import { createServer, Server } from "http"; 4 | import { SubscriptionServer } from "subscriptions-transport-ws"; 5 | import { useServer } from "graphql-ws/lib/use/ws"; 6 | import { execute, subscribe } from "graphql"; 7 | import { makeExecutableSchema } from "@graphql-tools/schema"; 8 | import { GraphQLFieldResolver } from "graphql"; 9 | import { PubSub } from "graphql-subscriptions"; 10 | import delay from "delay"; 11 | 12 | import request, { 13 | LEGACY_WEBSOCKET_PROTOCOL, 14 | supertestWs, 15 | WebSocketProtocol, 16 | } from "./"; 17 | import { WebSocketServer } from "ws"; 18 | 19 | const typeDefs = gql` 20 | type Query { 21 | hi(name: String): String! 22 | } 23 | type Mutation { 24 | do: String! 25 | } 26 | type Subscription { 27 | onHi(name: String): String! 28 | } 29 | `; 30 | 31 | type QueryHiResolver = GraphQLFieldResolver< 32 | never, 33 | ExpressContext, 34 | { name?: string }, 35 | string 36 | >; 37 | type MutationDoResolver = GraphQLFieldResolver< 38 | never, 39 | ExpressContext, 40 | never, 41 | string 42 | >; 43 | 44 | let server: Server; 45 | let queryHiResolver: jest.Mock>; 46 | let mutationDoResolver: jest.Mock>; 47 | const pubsub: PubSub = new PubSub(); 48 | 49 | type InitServerOptions = { 50 | graphqlPath?: string; 51 | wsProtocol?: WebSocketProtocol; 52 | }; 53 | 54 | const initTestServer = async ({ 55 | graphqlPath = "/graphql", 56 | wsProtocol = "graphql-transport-ws", 57 | }: InitServerOptions = {}): Promise => { 58 | const app = express(); 59 | const httpServer = createServer(app); 60 | queryHiResolver = jest.fn>( 61 | (_, { name = "" }) => `hi ${name}!` 62 | ); 63 | mutationDoResolver = jest.fn>( 64 | () => "done!" 65 | ); 66 | const schema = makeExecutableSchema({ 67 | typeDefs, 68 | resolvers: { 69 | Query: { 70 | hi: queryHiResolver, 71 | }, 72 | Mutation: { 73 | do: mutationDoResolver, 74 | }, 75 | Subscription: { 76 | onHi: { 77 | subscribe: () => pubsub.asyncIterator(["ON_HI"]), 78 | resolve: (_, { name }: { name?: string | null }) => { 79 | return `Hi ${name ? name : "unknown"}`; 80 | }, 81 | }, 82 | }, 83 | }, 84 | }); 85 | 86 | let closeWsMobile: () => Promise; 87 | if (wsProtocol === "graphql-ws") { 88 | const subscriptionServer = SubscriptionServer.create( 89 | { 90 | schema, 91 | execute, 92 | subscribe, 93 | }, 94 | { 95 | server: httpServer, 96 | } 97 | ); 98 | closeWsMobile = async () => subscriptionServer.close(); 99 | } else { 100 | const server = new WebSocketServer({ 101 | server: httpServer, 102 | path: graphqlPath, 103 | }); 104 | 105 | const serverCleanup = useServer({ schema }, server); 106 | closeWsMobile = async () => await serverCleanup.dispose(); 107 | } 108 | 109 | const server = new ApolloServer({ 110 | schema, 111 | context: (c) => c, 112 | plugins: [ 113 | { 114 | async serverWillStart() { 115 | return { 116 | async drainServer() { 117 | closeWsMobile(); 118 | }, 119 | }; 120 | }, 121 | }, 122 | ], 123 | }); 124 | await server.start(); 125 | server.applyMiddleware({ app, path: graphqlPath }); 126 | return httpServer; 127 | }; 128 | 129 | beforeEach(async () => { 130 | server = await initTestServer(); 131 | }); 132 | 133 | describe(".query()", () => { 134 | test("it queries", async () => { 135 | const { data } = await request<{ hi: string }>(server).query( 136 | gql` 137 | query { 138 | hi 139 | } 140 | ` 141 | ); 142 | expect(data?.hi).toBe("hi !"); 143 | }); 144 | describe("with variables", () => { 145 | test("it queries", async () => { 146 | const { data } = await request<{ hi: string }>(server).query( 147 | gql` 148 | query Greetings($name: String!) { 149 | hi(name: $name) 150 | } 151 | `, 152 | { name: "Alex" } 153 | ); 154 | expect(data?.hi).toBe("hi Alex!"); 155 | }); 156 | }); 157 | describe("with errors in return", () => { 158 | it("should make them available", async () => { 159 | queryHiResolver.mockImplementation(() => { 160 | throw new Error("Bad"); 161 | }); 162 | const { errors } = await request<{ hi: string }>(server).query( 163 | gql` 164 | query { 165 | hi 166 | } 167 | ` 168 | ); 169 | expect(errors).toBeDefined(); 170 | expect(errors).toHaveLength(1); 171 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 172 | expect(errors![0].message).toEqual("Bad"); 173 | }); 174 | }); 175 | }); 176 | 177 | describe(".mutate()", () => { 178 | test("it mutates", async () => { 179 | const { data } = await request<{ do: string }>(server).query( 180 | gql` 181 | mutation { 182 | do 183 | } 184 | ` 185 | ); 186 | expect(data?.do).toBe("done!"); 187 | }); 188 | }); 189 | 190 | describe(".variables()", () => { 191 | test("it queries with variables", async () => { 192 | const { data } = await request<{ hi: string }>(server) 193 | .query( 194 | gql` 195 | query Greetings($name: String!) { 196 | hi(name: $name) 197 | } 198 | ` 199 | ) 200 | .variables({ name: "Alex" }); 201 | expect(data?.hi).toBe("hi Alex!"); 202 | }); 203 | }); 204 | 205 | describe(".path()", () => { 206 | it("changes the path to query graphql", async () => { 207 | server = await initTestServer({ 208 | graphqlPath: "/specialUrl", 209 | }); 210 | const { data } = await request<{ hi: string }>(server) 211 | .path("/specialUrl") 212 | .query( 213 | gql` 214 | query Greetings($name: String!) { 215 | hi(name: $name) 216 | } 217 | ` 218 | ) 219 | .variables({ name: "Alex" }); 220 | expect(data?.hi).toBe("hi Alex!"); 221 | }); 222 | }); 223 | 224 | describe(".set()", () => { 225 | test("it properly set headers", async () => { 226 | await request<{ hi: string }>(server) 227 | .set("authorization", "bar") 228 | .query( 229 | gql` 230 | query { 231 | hi 232 | } 233 | ` 234 | ); 235 | expect(queryHiResolver).toHaveBeenCalled(); 236 | const { req } = queryHiResolver.mock.calls[0][2]; 237 | expect(req.headers["authorization"]).toEqual("bar"); 238 | }); 239 | }); 240 | 241 | describe(".auth()", () => { 242 | test("it properly set basic headers", async () => { 243 | await request<{ hi: string }>(server) 244 | .auth("username", "password") 245 | .query( 246 | gql` 247 | query { 248 | hi 249 | } 250 | ` 251 | ); 252 | expect(queryHiResolver).toHaveBeenCalled(); 253 | const { req } = queryHiResolver.mock.calls[0][2]; 254 | expect(req.headers["authorization"]).toEqual( 255 | "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" 256 | ); 257 | }); 258 | }); 259 | 260 | describe(".expectNoErrors()", () => { 261 | it("when there is an error it should throw", async () => { 262 | queryHiResolver.mockImplementation(() => { 263 | throw new Error("Bad"); 264 | }); 265 | return expect( 266 | request<{ hi: string }>(server) 267 | .query( 268 | gql` 269 | query { 270 | hi 271 | } 272 | ` 273 | ) 274 | .expectNoErrors() 275 | ).rejects.toThrow( 276 | "expected no errors but got 1 error(s) in GraphQL response: Bad" 277 | ); 278 | }); 279 | it("when there is no error it should not throw", async () => { 280 | return expect( 281 | request<{ hi: string }>(server) 282 | .query( 283 | gql` 284 | query { 285 | hi 286 | } 287 | ` 288 | ) 289 | .expectNoErrors() 290 | ).resolves.not.toThrow(); 291 | }); 292 | }); 293 | 294 | describe("test ws legacy", () => { 295 | beforeEach(async () => { 296 | server = await initTestServer({ wsProtocol: "graphql-ws" }); 297 | await new Promise((res) => 298 | server.listen(0, "localhost", () => res()) 299 | ); 300 | }); 301 | 302 | afterEach(async () => { 303 | await supertestWs.end(); 304 | server.close(); 305 | }); 306 | 307 | it("should work", async () => { 308 | const sub = await supertestWs(server).protocol(LEGACY_WEBSOCKET_PROTOCOL) 309 | .subscribe(gql` 310 | subscription { 311 | onHi 312 | } 313 | `); 314 | 315 | // there is no wayt to know if the subscription is active, 316 | // to avoid race conditions we need to wait a bit 317 | await delay(200); 318 | 319 | pubsub.publish("ON_HI", {}); 320 | const res = await sub.next().expectNoErrors(); 321 | 322 | expect(res.data).toEqual({ onHi: "Hi unknown" }); 323 | }); 324 | }); 325 | 326 | describe("test ws", () => { 327 | beforeEach(async () => { 328 | server = await initTestServer(); 329 | await new Promise((res) => 330 | server.listen(0, "localhost", () => res()) 331 | ); 332 | }); 333 | 334 | afterEach(async () => { 335 | await supertestWs.end(); 336 | server.close(); 337 | }); 338 | 339 | it("should work", async () => { 340 | const sub = await supertestWs(server).subscribe(gql` 341 | subscription { 342 | onHi 343 | } 344 | `); 345 | 346 | // there is no wayt to know if the subscription is active, 347 | // to avoid race conditions we need to wait a bit 348 | await delay(200); 349 | 350 | pubsub.publish("ON_HI", {}); 351 | const res = await sub.next().expectNoErrors(); 352 | 353 | expect(res.data).toEqual({ onHi: "Hi unknown" }); 354 | }); 355 | }); 356 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionResult } from "graphql"; 2 | 3 | export type Variables = { [key: string]: unknown }; 4 | 5 | export type AssertFn = ( 6 | result: ExecutionResult 7 | ) => Error | undefined | Promise; 8 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, GraphQLError, OperationDefinitionNode } from "graphql"; 2 | import { AssertFn } from "./types"; 3 | 4 | export const getOperationName = ( 5 | document: DocumentNode 6 | ): string | undefined => { 7 | let operationName = undefined; 8 | 9 | const operationDefinitions = document.definitions.filter( 10 | (definition) => definition.kind === "OperationDefinition" 11 | ) as OperationDefinitionNode[]; 12 | 13 | if (operationDefinitions.length === 1) { 14 | operationName = operationDefinitions[0].name?.value; 15 | } 16 | return operationName; 17 | }; 18 | 19 | type Pop = { 20 | resolve: (item: TItem | PromiseLike) => void; 21 | tm: NodeJS.Timeout | null; 22 | }; 23 | 24 | // inspired from https://github.com/davidje13/superwstest/blob/main/src/BlockingQueue.mjs 25 | export class BlockingQueue { 26 | private pendingPush: TItem[] = []; 27 | private pendingPop: Pop[] = []; 28 | 29 | push(item: TItem): void { 30 | if (this.pendingPop.length > 0) { 31 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it's verified with above condition 32 | const firstPendingPop = this.pendingPop.shift()!; 33 | if (typeof firstPendingPop.tm === "number") { 34 | clearTimeout(firstPendingPop.tm); 35 | } 36 | firstPendingPop.resolve(item); 37 | } else { 38 | this.pendingPush.push(item); 39 | } 40 | } 41 | 42 | async pop(timeout?: number): Promise { 43 | if (this.pendingPush.length > 0) { 44 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it's verified with above condition 45 | return this.pendingPush.shift()!; 46 | } 47 | return new Promise((resolve, reject) => { 48 | const newPop: Pop = { resolve, tm: null }; 49 | this.pendingPop.push(newPop); 50 | if (timeout !== undefined) { 51 | newPop.tm = setTimeout(() => { 52 | this.pendingPop = this.pendingPop.filter((pop) => pop !== newPop); 53 | reject(new Error(`Timeout after ${timeout}ms`)); 54 | }, timeout); 55 | } 56 | }); 57 | } 58 | 59 | flush(): TItem[] { 60 | const flushed = [...this.pendingPush]; 61 | this.pendingPush = []; 62 | return flushed; 63 | } 64 | 65 | get length(): number { 66 | return this.pendingPush.length; 67 | } 68 | } 69 | 70 | /** 71 | * Wraps an assert function into another. 72 | * The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it. 73 | * 74 | * Borrowed from supertest 75 | */ 76 | export function wrapAssertFn( 77 | assertFn: AssertFn 78 | ): AssertFn { 79 | const savedStack = new Error().stack?.split("\n").slice(3) || []; 80 | 81 | return async (res) => { 82 | let badStack; 83 | const err = await assertFn(res); 84 | if (err instanceof Error && err.stack) { 85 | badStack = err.stack.replace(err.message, "").split("\n").slice(1); 86 | err.stack = [err.toString()] 87 | .concat(savedStack) 88 | .concat("----") 89 | .concat(badStack) 90 | .join("\n"); 91 | } 92 | return err; 93 | }; 94 | } 95 | 96 | export const asserNoError: AssertFn = ({ errors }) => { 97 | if (errors && Array.isArray(errors) && errors.length > 0) { 98 | const errorSummary = (errors as GraphQLError[]) 99 | .map((e) => e.message) 100 | .join(","); 101 | return new Error( 102 | `expected no errors but got ${errors.length} error(s) in GraphQL response: ${errorSummary}` 103 | ); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "transpileOnly": true 4 | }, 5 | "compilerOptions": { 6 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 7 | 8 | /* Projects */ 9 | // "incremental": true, /* Enable incremental compilation */ 10 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 11 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 12 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 13 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 14 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 15 | 16 | /* Language and Environment */ 17 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 18 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 19 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | 29 | /* Modules */ 30 | "module": "commonjs", /* Specify what module code is generated. */ 31 | "rootDir": "./src", /* Specify the root folder within your source files. */ 32 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 | // "resolveJsonModule": true, /* Enable importing .json files */ 40 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 41 | 42 | /* JavaScript Support */ 43 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 44 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 45 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 46 | 47 | /* Emit */ 48 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 49 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 50 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 51 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 52 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 53 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 54 | // "removeComments": true, /* Disable emitting comments. */ 55 | // "noEmit": true, /* Disable emitting files from a compilation. */ 56 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 57 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 58 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 59 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 62 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 63 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 64 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 65 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 66 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 67 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 68 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 69 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 70 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 71 | 72 | /* Interop Constraints */ 73 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 74 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 75 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 76 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 77 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 78 | 79 | /* Type Checking */ 80 | "strict": true, /* Enable all strict type-checking options. */ 81 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 82 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 83 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 84 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 85 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 86 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 87 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 88 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 89 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 90 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 91 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 92 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 93 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 94 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 95 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 96 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 97 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 98 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 99 | 100 | /* Completeness */ 101 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 102 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 103 | } 104 | } 105 | --------------------------------------------------------------------------------