From 0fb70621df68e9b67ff58c0025d485fe27065419 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Sat, 18 Feb 2023 11:41:46 -0500 Subject: [PATCH] refactor: introduce eslint --- .eslintignore | 5 + .eslintrc | 65 ++ examples/authentication-via-http-header.ts | 6 +- examples/batching-requests.ts | 6 +- examples/cookie-support-for-node.ts | 6 +- examples/custom-fetch.ts | 4 +- examples/error-handling.ts | 2 +- examples/passing-custom-header-per-request.ts | 8 +- examples/passing-more-options-to-fetch.ts | 6 +- examples/receiving-a-raw-response.ts | 2 +- examples/typed-document-node.ts | 9 +- examples/using-variables.ts | 4 +- package.json | 11 + pnpm-lock.yaml | 639 ++++++++++++++++++ src/createRequestBody.ts | 29 +- src/defaultJsonSerializer.ts | 2 +- src/graphql-ws.ts | 27 +- src/helpers.ts | 18 + src/index.ts | 516 +++++++------- src/parseArgs.ts | 41 +- src/resolveRequestDocument.ts | 15 +- src/types.ts | 43 +- tests/__helpers.ts | 24 +- tests/batching.test.ts | 36 +- tests/custom-fetch.test.ts | 6 +- tests/document-node.test.ts | 8 +- tests/endpoint.test.ts | 12 +- tests/errorPolicy.test.ts | 24 +- tests/general.test.ts | 132 ++-- tests/gql.test.ts | 10 +- tests/headers.test.ts | 116 ++-- tests/json-serializer.test.ts | 104 +-- tests/signal.test.ts | 80 +-- tests/typed-document-node.test.ts | 36 +- 34 files changed, 1373 insertions(+), 679 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 src/helpers.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..0a92c1eb7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +.yalc +.vscode +.github +node_modules +build diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..f58e82956 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,65 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": ["tsconfig.json"] + }, + "plugins": [ + // This enables using new lint rules powered by the TypeScript compiler API. + // https://github.com/typescript-eslint/typescript-eslint + "@typescript-eslint", + // This makes it so the IDE reports lint rejections as warnings only. This is + // better than errors because most lint rejections are not runtime errors. This + // allows IDE errors to be exclusive for e.g. static type errors which often are + // reflective of real runtime errors. + // https://github.com/bfanger/eslint-plugin-only-warn + "only-warn", + // This enables the use of a lint rule to reject function declarations. This is + // preferable as a way to encourage higher order function usage. For example it is not + // possible to wrap a function declaration with Open Telemetry instrumentation but it is + // possible to wrap an arrow function since its an expression. + // https://github.com/TristonJ/eslint-plugin-prefer-arrow + "prefer-arrow", + // This enables the use of a lint rule to reject use of @deprecated functions. + // https://github.com/gund/eslint-plugin-deprecation + "deprecation", + // Import sorting integrated into ESLint. + // https://github.com/lydell/eslint-plugin-simple-import-sort + "simple-import-sort", + // https://github.com/microsoft/tsdoc/tree/master/eslint-plugin + "tsdoc" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "prettier" + ], + "overrides": [], + "rules": { + "@typescript-eslint/consistent-type-imports": "warn", + // Enforce backticks + // Note you must disable the base rule as it can report incorrect errors. + "quotes": "off", + "@typescript-eslint/quotes": ["warn", "backtick"], + "tsdoc/syntax": "warn", + "simple-import-sort/imports": [ + "warn", + { + "groups": [] + } + ], + "simple-import-sort/exports": "warn", + "deprecation/deprecation": "warn", + "prefer-arrow/prefer-arrow-functions": "warn", + // TypeScript makes these safe & effective + "no-case-declarations": "off", + // Same approach used by TypeScript noUnusedLocals + "@typescript-eslint/no-unused-vars": ["warn", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }], + // Needed when working with .mts/.cts where a lone e.g. is not allowed + "@typescript-eslint/no-unnecessary-type-constraint": "off", + // Useful for organizing Types + "@typescript-eslint/no-namespace": "off" + } +} diff --git a/examples/authentication-via-http-header.ts b/examples/authentication-via-http-header.ts index 87fa0eba4..d4564e4b4 100644 --- a/examples/authentication-via-http-header.ts +++ b/examples/authentication-via-http-header.ts @@ -1,10 +1,10 @@ import { GraphQLClient } from '../src/index.js' -;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' +;(async () => { + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const graphQLClient = new GraphQLClient(endpoint, { headers: { - authorization: 'Bearer MY_TOKEN', + authorization: `Bearer MY_TOKEN`, }, }) diff --git a/examples/batching-requests.ts b/examples/batching-requests.ts index 2b4f5b850..9ac7c4146 100644 --- a/examples/batching-requests.ts +++ b/examples/batching-requests.ts @@ -1,6 +1,6 @@ import { batchRequests } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.spacex.land/graphql/' + const endpoint = `https://api.spacex.land/graphql/` const query1 = /* GraphQL */ ` query ($id: ID!) { @@ -11,7 +11,7 @@ import { batchRequests } from '../src/index.js' } ` const variables1 = { - id: 'C105', + id: `C105`, } interface TData1 { @@ -41,7 +41,7 @@ import { batchRequests } from '../src/index.js' ` const variables3 = { - id: 'B1015', + id: `B1015`, } interface TData3 { diff --git a/examples/cookie-support-for-node.ts b/examples/cookie-support-for-node.ts index ae4fbd4c7..0f9561d65 100644 --- a/examples/cookie-support-for-node.ts +++ b/examples/cookie-support-for-node.ts @@ -1,12 +1,12 @@ -;(global as any).fetch = require('fetch-cookie/node-fetch')(require('node-fetch')) +;(global as any).fetch = require(`fetch-cookie/node-fetch`)(require(`node-fetch`)) import { GraphQLClient } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const graphQLClient = new GraphQLClient(endpoint, { headers: { - authorization: 'Bearer MY_TOKEN', + authorization: `Bearer MY_TOKEN`, }, }) diff --git a/examples/custom-fetch.ts b/examples/custom-fetch.ts index 4ca167ea4..bc9704196 100644 --- a/examples/custom-fetch.ts +++ b/examples/custom-fetch.ts @@ -1,7 +1,7 @@ -import fetch from 'cross-fetch' import { GraphQLClient } from '../src/index.js' +import fetch from 'cross-fetch' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const graphQLClient = new GraphQLClient(endpoint, { fetch: fetch }) diff --git a/examples/error-handling.ts b/examples/error-handling.ts index 24a490646..ef6ebb682 100644 --- a/examples/error-handling.ts +++ b/examples/error-handling.ts @@ -1,6 +1,6 @@ import { request } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const query = /* GraphQL */ ` { diff --git a/examples/passing-custom-header-per-request.ts b/examples/passing-custom-header-per-request.ts index e682d313a..0a9a1ee36 100644 --- a/examples/passing-custom-header-per-request.ts +++ b/examples/passing-custom-header-per-request.ts @@ -1,10 +1,10 @@ import { GraphQLClient } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const client = new GraphQLClient(endpoint, { headers: { - authorization: 'Bearer MY_TOKEN', + authorization: `Bearer MY_TOKEN`, }, }) @@ -20,8 +20,8 @@ import { GraphQLClient } from '../src/index.js' ` const requestHeaders = { - authorization: 'Bearer MY_TOKEN_2', - 'x-custom': 'foo', + authorization: `Bearer MY_TOKEN_2`, + 'x-custom': `foo`, } interface TData { diff --git a/examples/passing-more-options-to-fetch.ts b/examples/passing-more-options-to-fetch.ts index aa0811ff9..ceef1eb41 100644 --- a/examples/passing-more-options-to-fetch.ts +++ b/examples/passing-more-options-to-fetch.ts @@ -1,10 +1,10 @@ import { GraphQLClient } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const graphQLClient = new GraphQLClient(endpoint, { - credentials: 'include', - mode: 'cors', + credentials: `include`, + mode: `cors`, }) const query = /* GraphQL */ ` diff --git a/examples/receiving-a-raw-response.ts b/examples/receiving-a-raw-response.ts index da9995b8d..597534637 100644 --- a/examples/receiving-a-raw-response.ts +++ b/examples/receiving-a-raw-response.ts @@ -1,6 +1,6 @@ import { rawRequest } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const query = /* GraphQL */ ` { diff --git a/examples/typed-document-node.ts b/examples/typed-document-node.ts index f82eca541..1f340059e 100644 --- a/examples/typed-document-node.ts +++ b/examples/typed-document-node.ts @@ -1,9 +1,8 @@ -import { TypedDocumentNode } from '@graphql-typed-document-node/core' +import { GraphQLClient,request } from '../src/index.js' +import type { TypedDocumentNode } from '@graphql-typed-document-node/core' import { parse } from 'graphql' - -import { request, GraphQLClient } from '../src/index.js' ;(async function () { - const endpoint = 'https://graphql-yoga.com/api/graphql' + const endpoint = `https://graphql-yoga.com/api/graphql` const query: TypedDocumentNode<{ greetings: string }, never | Record> = parse(/* GraphQL */ ` query greetings { @@ -18,7 +17,7 @@ import { request, GraphQLClient } from '../src/index.js' console.log(data.greetings) })().catch(console.error) ;(async function () { - const endpoint = 'https://graphql-yoga.com/api/graphql' + const endpoint = `https://graphql-yoga.com/api/graphql` const client = new GraphQLClient(endpoint) diff --git a/examples/using-variables.ts b/examples/using-variables.ts index e9b1db21d..168ff446d 100644 --- a/examples/using-variables.ts +++ b/examples/using-variables.ts @@ -1,6 +1,6 @@ import { request } from '../src/index.js' ;(async function () { - const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr' + const endpoint = `https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr` const query = /* GraphQL */ ` query getMovie($title: String!) { @@ -14,7 +14,7 @@ import { request } from '../src/index.js' ` const variables = { - title: 'Inception', + title: `Inception`, } interface TData { diff --git a/package.json b/package.json index b89de7c03..596d99a95 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,10 @@ "build:docs": "doctoc README.md --notitle", "dev": "rm -rf dist && tsc --watch", "format": "prettier --write .", + "lint": "eslint . --ext .ts,.tsx --fix", "check:types": "pnpm tsc --noEmit", "check:format": "prettier --check .", + "check:lint": "eslint . --ext .ts,.tsx --max-warnings 0", "prepublishOnly": "pnpm build", "build": "pnpm clean && pnpm build:cjs && pnpm build:esm", "build:cjs": "pnpm tsc --project tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > build/cjs/package.json", @@ -74,11 +76,20 @@ "@types/extract-files": "^8.1.1", "@types/node": "^18.11.18", "@types/ws": "^8.5.4", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", "@vitest/coverage-c8": "^0.28.2", "apollo-server-express": "^3.11.1", "body-parser": "^1.20.1", "doctoc": "^2.2.1", "dripip": "^0.10.0", + "eslint": "^8.34.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-deprecation": "^1.3.3", + "eslint-plugin-only-warn": "^1.1.0", + "eslint-plugin-prefer-arrow": "^1.2.3", + "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-tsdoc": "^0.2.17", "express": "^4.18.2", "fetch-cookie": "^2.1.0", "get-port": "^6.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 132787122..bbc5ffd26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,12 +9,21 @@ specifiers: '@types/extract-files': ^8.1.1 '@types/node': ^18.11.18 '@types/ws': ^8.5.4 + '@typescript-eslint/eslint-plugin': ^5.52.0 + '@typescript-eslint/parser': ^5.52.0 '@vitest/coverage-c8': ^0.28.2 apollo-server-express: ^3.11.1 body-parser: ^1.20.1 cross-fetch: ^3.1.5 doctoc: ^2.2.1 dripip: ^0.10.0 + eslint: ^8.34.0 + eslint-config-prettier: ^8.6.0 + eslint-plugin-deprecation: ^1.3.3 + eslint-plugin-only-warn: ^1.1.0 + eslint-plugin-prefer-arrow: ^1.2.3 + eslint-plugin-simple-import-sort: ^10.0.0 + eslint-plugin-tsdoc: ^0.2.17 express: ^4.18.2 extract-files: ^9.0.0 fetch-cookie: ^2.1.0 @@ -44,11 +53,20 @@ devDependencies: '@types/extract-files': 8.1.1 '@types/node': 18.11.18 '@types/ws': 8.5.4 + '@typescript-eslint/eslint-plugin': 5.52.0_2facupdxlvm55gtp63dnpqcawi + '@typescript-eslint/parser': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a '@vitest/coverage-c8': 0.28.2_happy-dom@8.1.5 apollo-server-express: 3.11.1_5ha345eiak62qiwwva5z4dp6gu body-parser: 1.20.1 doctoc: 2.2.1 dripip: 0.10.0 + eslint: 8.34.0 + eslint-config-prettier: 8.6.0_eslint@8.34.0 + eslint-plugin-deprecation: 1.3.3_ehfyfk7qbmgzg5nk6xmobqdh3a + eslint-plugin-only-warn: 1.1.0 + eslint-plugin-prefer-arrow: 1.2.3_eslint@8.34.0 + eslint-plugin-simple-import-sort: 10.0.0_eslint@8.34.0 + eslint-plugin-tsdoc: 0.2.17 express: 4.18.2 fetch-cookie: 2.1.0 get-port: 6.1.2 @@ -398,6 +416,23 @@ packages: dev: true optional: true + /@eslint/eslintrc/1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@graphql-tools/merge/8.3.12_graphql@16.6.0: resolution: {integrity: sha512-BFL8r4+FrqecPnIW0H8UJCBRQ4Y8Ep60aujw9c/sQuFmQTiqgWgpphswMGfaosP2zUinDE3ojU5wwcS2IJnumA==} peerDependencies: @@ -480,6 +515,26 @@ packages: graphql: 16.6.0 dev: false + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + /@istanbuljs/schema/0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} @@ -517,6 +572,19 @@ packages: resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} dev: true + /@microsoft/tsdoc-config/0.16.2: + resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} + dependencies: + '@microsoft/tsdoc': 0.14.2 + ajv: 6.12.6 + jju: 1.4.0 + resolve: 1.19.0 + dev: true + + /@microsoft/tsdoc/0.14.2: + resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + dev: true + /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -860,6 +928,10 @@ packages: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: true + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + /@types/long/4.0.2: resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} dev: true @@ -904,6 +976,10 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + /@types/serve-static/1.13.10: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: @@ -921,6 +997,149 @@ packages: '@types/node': 18.11.18 dev: true + /@typescript-eslint/eslint-plugin/5.52.0_2facupdxlvm55gtp63dnpqcawi: + resolution: {integrity: sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/type-utils': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + '@typescript-eslint/utils': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + debug: 4.3.4 + eslint: 8.34.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/experimental-utils/5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a: + resolution: {integrity: sha512-kd8CRr04mNE3hw4et6+0T0NI5vli2H6dJCGzjX1r12s/FXUehLVadmvo2Nl3DN80YqAh1cVC6zYZAkpmGiVJ5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + eslint: 8.34.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/parser/5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a: + resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.4 + debug: 4.3.4 + eslint: 8.34.0 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager/5.52.0: + resolution: {integrity: sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/visitor-keys': 5.52.0 + dev: true + + /@typescript-eslint/type-utils/5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a: + resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.4 + '@typescript-eslint/utils': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + debug: 4.3.4 + eslint: 8.34.0 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types/5.52.0: + resolution: {integrity: sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree/5.52.0_typescript@4.9.4: + resolution: {integrity: sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/visitor-keys': 5.52.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils/5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a: + resolution: {integrity: sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.52.0 + '@typescript-eslint/types': 5.52.0 + '@typescript-eslint/typescript-estree': 5.52.0_typescript@4.9.4 + eslint: 8.34.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.34.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys/5.52.0: + resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.52.0 + eslint-visitor-keys: 3.3.0 + dev: true + /@vitest/coverage-c8/0.28.2_happy-dom@8.1.5: resolution: {integrity: sha512-BWiOUk+d5LvK/9pKaYbL8eLng2EFXgTQMH9QN5nOoizWWKXGNO6LjduVpoz8ZQfb8/6tMVhae7SAS+w0zkRkNw==} dependencies: @@ -982,6 +1201,14 @@ packages: negotiator: 0.6.3 dev: true + /acorn-jsx/5.3.2_acorn@8.8.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.2 + dev: true + /acorn-walk/8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} @@ -1175,6 +1402,10 @@ packages: - encoding dev: true + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + /array-flatten/1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} dev: true @@ -1312,6 +1543,11 @@ packages: get-intrinsic: 1.2.0 dev: true + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + /caseless/0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true @@ -1548,6 +1784,10 @@ packages: type-detect: 4.0.8 dev: true + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1596,6 +1836,13 @@ packages: - supports-color dev: true + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + /dom-serializer/1.3.2: resolution: {integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==} dependencies: @@ -1739,6 +1986,180 @@ packages: engines: {node: '>=10'} dev: true + /eslint-config-prettier/8.6.0_eslint@8.34.0: + resolution: {integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.34.0 + dev: true + + /eslint-plugin-deprecation/1.3.3_ehfyfk7qbmgzg5nk6xmobqdh3a: + resolution: {integrity: sha512-Bbkv6ZN2cCthVXz/oZKPwsSY5S/CbgTLRG4Q2s2gpPpgNsT0uJ0dB5oLNiWzFYY8AgKX4ULxXFG1l/rDav9QFA==} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: ^3.7.5 || ^4.0.0 + dependencies: + '@typescript-eslint/experimental-utils': 5.52.0_ehfyfk7qbmgzg5nk6xmobqdh3a + eslint: 8.34.0 + tslib: 2.4.1 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-only-warn/1.1.0: + resolution: {integrity: sha512-2tktqUAT+Q3hCAU0iSf4xAN1k9zOpjK5WO8104mB0rT/dGhOa09582HN5HlbxNbPRZ0THV7nLGvzugcNOSjzfA==} + engines: {node: '>=6'} + dev: true + + /eslint-plugin-prefer-arrow/1.2.3_eslint@8.34.0: + resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==} + peerDependencies: + eslint: '>=2.0.0' + dependencies: + eslint: 8.34.0 + dev: true + + /eslint-plugin-simple-import-sort/10.0.0_eslint@8.34.0: + resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==} + peerDependencies: + eslint: '>=5.0.0' + dependencies: + eslint: 8.34.0 + dev: true + + /eslint-plugin-tsdoc/0.2.17: + resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} + dependencies: + '@microsoft/tsdoc': 0.14.2 + '@microsoft/tsdoc-config': 0.16.2 + dev: true + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.34.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.34.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.34.0: + resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.34.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.4.2 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.1 + is-path-inside: 3.0.3 + js-sdsl: 4.3.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.2 + acorn-jsx: 5.3.2_acorn@8.8.2 + eslint-visitor-keys: 3.3.0 + dev: true + + /esquery/1.4.2: + resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + /etag/1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1806,6 +2227,17 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.4 + dev: true + /fast-glob/3.2.7: resolution: {integrity: sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==} engines: {node: '>=8'} @@ -1821,6 +2253,10 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + /fastq/1.11.1: resolution: {integrity: sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==} dependencies: @@ -1840,6 +2276,13 @@ packages: tough-cookie: 4.1.2 dev: true + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -1870,6 +2313,18 @@ packages: path-exists: 4.0.0 dev: true + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + /foreground-child/2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} @@ -1987,6 +2442,13 @@ packages: is-glob: 4.0.1 dev: true + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob/7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} dependencies: @@ -1998,6 +2460,13 @@ packages: path-is-absolute: 1.0.1 dev: true + /globals/13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + /globby/11.0.4: resolution: {integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==} engines: {node: '>=10'} @@ -2010,10 +2479,26 @@ packages: slash: 3.0.0 dev: true + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + /graceful-fs/4.2.6: resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} dev: true + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + /graphql-tag/2.12.6_graphql@16.6.0: resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} engines: {node: '>=10'} @@ -2143,6 +2628,24 @@ packages: engines: {node: '>= 4'} dev: true + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + /indent-string/4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -2227,6 +2730,13 @@ packages: is-extglob: 2.1.1 dev: true + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + /is-hexadecimal/1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} dev: true @@ -2236,6 +2746,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + /is-plain-obj/2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -2305,6 +2820,21 @@ packages: istanbul-lib-report: 3.0.0 dev: true + /jju/1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + dev: true + + /js-sdsl/4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + /jsbn/0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} dev: true @@ -2317,6 +2847,10 @@ packages: resolution: {integrity: sha512-a3xHnILGMtk+hDOqNwHzF6e2fNbiMrXZvxKQiEv2MlgQP+pjIOzqAmKYD2mDpXYE/44M7g+n9p2bKkYWDUcXCQ==} dev: true + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + /json-stringify-safe/5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: true @@ -2341,6 +2875,14 @@ packages: verror: 1.10.0 dev: true + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + /local-pkg/0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} @@ -2357,6 +2899,10 @@ packages: resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} dev: true + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + /lodash.sortby/4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true @@ -2672,6 +3218,14 @@ packages: hasBin: true dev: true + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + /negotiator/0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2731,6 +3285,18 @@ packages: wrappy: 1.0.2 dev: true + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + /p-limit/3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2756,6 +3322,13 @@ packages: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} dev: true + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + /parse-entities/2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} dependencies: @@ -2857,6 +3430,11 @@ packages: source-map-js: 1.0.2 dev: true + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + /prettier/2.8.3: resolution: {integrity: sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==} engines: {node: '>=10.13.0'} @@ -2943,6 +3521,11 @@ packages: util-deprecate: 1.0.2 dev: true + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + /remark-footnotes/3.0.0: resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} dependencies: @@ -3017,6 +3600,18 @@ packages: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve/1.19.0: + resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + dev: true + /resolve/1.22.1: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true @@ -3085,6 +3680,14 @@ packages: lru-cache: 6.0.0 dev: true + /semver/7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /send/0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -3296,6 +3899,11 @@ packages: ansi-regex: 6.0.1 dev: true + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + /strip-literal/1.0.0: resolution: {integrity: sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==} dependencies: @@ -3330,6 +3938,10 @@ packages: minimatch: 3.1.2 dev: true + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + /tinybench/2.3.1: resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} dev: true @@ -3393,6 +4005,16 @@ packages: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} dev: true + /tsutils/3.21.0_typescript@4.9.4: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.9.4 + dev: true + /tunnel-agent/0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -3403,11 +4025,23 @@ packages: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} dev: true + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + /type-detect/4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} dev: true + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + /type-fest/3.5.3: resolution: {integrity: sha512-V2+og4j/rWReWvaFrse3s9g2xvUv/K9Azm/xo6CjIuq7oeGqsoimC7+9/A3tfvNcbQf8RPSVj/HV81fB4DJrjA==} engines: {node: '>=14.16'} @@ -3727,6 +4361,11 @@ packages: string-width: 4.2.3 dev: true + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + /wrap-ansi/4.0.0: resolution: {integrity: sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==} engines: {node: '>=6'} diff --git a/src/createRequestBody.ts b/src/createRequestBody.ts index e08aeeae6..7272701ba 100644 --- a/src/createRequestBody.ts +++ b/src/createRequestBody.ts @@ -1,8 +1,9 @@ -import { isExtractableFile, extractFiles, ExtractableFile } from 'extract-files' -import FormDataNode from 'form-data' import { defaultJsonSerializer } from './defaultJsonSerializer.js' -import { Variables } from './types.js' -import * as Dom from './types.dom.js' +import type * as Dom from './types.dom.js' +import type { Variables } from './types.js' +import type { ExtractableFile } from 'extract-files' +import { extractFiles, isExtractableFile } from 'extract-files' +import FormDataNode from 'form-data' /** * Duck type if NodeJS stream @@ -10,28 +11,28 @@ import * as Dom from './types.dom.js' */ const isExtractableFileEnhanced = (value: any): value is ExtractableFile | { pipe: Function } => isExtractableFile(value) || - (value !== null && typeof value === 'object' && typeof value.pipe === 'function') + (value !== null && typeof value === `object` && typeof value.pipe === `function`) /** * Returns Multipart Form if body contains files * (https://github.com/jaydenseric/graphql-multipart-request-spec) * Otherwise returns JSON */ -export default function createRequestBody( +const createRequestBody = ( query: string | string[], variables?: Variables | Variables[], operationName?: string, jsonSerializer = defaultJsonSerializer -): string | Dom.FormData { - const { clone, files } = extractFiles({ query, variables, operationName }, '', isExtractableFileEnhanced) +): string | Dom.FormData => { + const { clone, files } = extractFiles({ query, variables, operationName }, ``, isExtractableFileEnhanced) if (files.size === 0) { if (!Array.isArray(query)) { return jsonSerializer.stringify(clone) } - if (typeof variables !== 'undefined' && !Array.isArray(variables)) { - throw new Error('Cannot create request body with given variable type, array expected') + if (typeof variables !== `undefined` && !Array.isArray(variables)) { + throw new Error(`Cannot create request body with given variable type, array expected`) } // Batch support @@ -46,18 +47,18 @@ export default function createRequestBody( return jsonSerializer.stringify(payload) } - const Form = typeof FormData === 'undefined' ? FormDataNode : FormData + const Form = typeof FormData === `undefined` ? FormDataNode : FormData const form = new Form() - form.append('operations', jsonSerializer.stringify(clone)) + form.append(`operations`, jsonSerializer.stringify(clone)) const map: { [key: number]: string[] } = {} let i = 0 files.forEach((paths) => { map[++i] = paths }) - form.append('map', jsonSerializer.stringify(map)) + form.append(`map`, jsonSerializer.stringify(map)) i = 0 files.forEach((paths, file) => { @@ -66,3 +67,5 @@ export default function createRequestBody( return form as Dom.FormData } + +export default createRequestBody diff --git a/src/defaultJsonSerializer.ts b/src/defaultJsonSerializer.ts index 2c5cb3f8f..61622bf47 100644 --- a/src/defaultJsonSerializer.ts +++ b/src/defaultJsonSerializer.ts @@ -1,4 +1,4 @@ -import { JsonSerializer } from './types.dom.js' +import type { JsonSerializer } from './types.dom.js' export const defaultJsonSerializer: JsonSerializer = { parse: JSON.parse, diff --git a/src/graphql-ws.ts b/src/graphql-ws.ts index f86c39922..51966f611 100644 --- a/src/graphql-ws.ts +++ b/src/graphql-ws.ts @@ -1,16 +1,17 @@ -import { ClientError, RequestDocument, Variables } from './types.js' -import * as Dom from './types.dom.js' import { resolveRequestDocument } from './resolveRequestDocument.js' +import type * as Dom from './types.dom.js' +import type { RequestDocument, Variables } from './types.js'; +import { ClientError } from './types.js' // import type WebSocket from 'ws' -const CONNECTION_INIT = 'connection_init' -const CONNECTION_ACK = 'connection_ack' -const PING = 'ping' -const PONG = 'pong' -const SUBSCRIBE = 'subscribe' -const NEXT = 'next' -const ERROR = 'error' -const COMPLETE = 'complete' +const CONNECTION_INIT = `connection_init` +const CONNECTION_ACK = `connection_ack` +const PING = `ping` +const PONG = `pong` +const SUBSCRIBE = `subscribe` +const NEXT = `next` +const ERROR = `error` +const COMPLETE = `complete` type MessagePayload = { [key: string]: any } @@ -84,7 +85,7 @@ type SocketState = { } export class GraphQLWebSocketClient { - static PROTOCOL: string = 'graphql-transport-ws' + static PROTOCOL = `graphql-transport-ws` private socket: WebSocket private socketState: SocketState = { acknowledged: false, lastRequestId: 0, subscriptions: {} } @@ -113,7 +114,7 @@ export class GraphQLWebSocketClient { switch (message.type) { case CONNECTION_ACK: { if (this.socketState.acknowledged) { - console.warn('Duplicate CONNECTION_ACK message ignored') + console.warn(`Duplicate CONNECTION_ACK message ignored`) } else { this.socketState.acknowledged = true if (onAcknowledged) onAcknowledged(message.payload) @@ -174,7 +175,7 @@ export class GraphQLWebSocketClient { console.error(e) socket.close(1006) } - socket.close(4400, 'Unknown graphql-ws message.') + socket.close(4400, `Unknown graphql-ws message.`) } } diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 000000000..8d0e89449 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,18 @@ +import type * as Dom from './types.dom.js' + +export type RemoveIndex = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] +} + +export const uppercase = (str: S): Uppercase => str.toUpperCase() as Uppercase + +/** + * Convert Headers instance into regular object + */ +export const HeadersInstanceToPlainObject = (headers: Dom.Response['headers']): Record => { + const o: Record = {} + headers.forEach((v, k) => { + o[k] = v + }) + return o +} diff --git a/src/index.ts b/src/index.ts index ccfe500e7..38877a059 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,46 +1,49 @@ -import crossFetch, * as CrossFetch from 'cross-fetch' -import { TypedDocumentNode } from '@graphql-typed-document-node/core' import createRequestBody from './createRequestBody.js' import { defaultJsonSerializer } from './defaultJsonSerializer.js' +import { HeadersInstanceToPlainObject, uppercase } from './helpers.js' import { parseBatchRequestArgs, - parseRawRequestArgs, - parseRequestArgs, parseBatchRequestsExtendedArgs, + parseRawRequestArgs, parseRawRequestExtendedArgs, + parseRequestArgs, parseRequestExtendedArgs, } from './parseArgs.js' +import { resolveRequestDocument } from './resolveRequestDocument.js' +import type * as Dom from './types.dom.js' +import type { + HTTPMethodInput, + MaybeFunction, + RequestConfig, + RequestMiddleware, + Response, + VariablesAndRequestHeadersArgs, +} from './types.js' import { BatchRequestDocument, + BatchRequestsExtendedOptions, BatchRequestsOptions, ClientError, + RawRequestExtendedOptions, RawRequestOptions, RequestDocument, - RequestOptions, - BatchRequestsExtendedOptions, - RawRequestExtendedOptions, RequestExtendedOptions, + RequestOptions, Variables, - PatchedRequestInit, - MaybeFunction, - Response, - RemoveIndex, - RequestMiddleware, - VariablesAndRequestHeaders, } from './types.js' -import * as Dom from './types.dom.js' -import { resolveRequestDocument } from './resolveRequestDocument.js' +import type { TypedDocumentNode } from '@graphql-typed-document-node/core' +import crossFetch, * as CrossFetch from 'cross-fetch' export { BatchRequestDocument, - BatchRequestsOptions, BatchRequestsExtendedOptions, + BatchRequestsOptions, ClientError, - RawRequestOptions, RawRequestExtendedOptions, + RawRequestOptions, RequestDocument, - RequestOptions, RequestExtendedOptions, + RequestOptions, Variables, } @@ -51,7 +54,7 @@ const resolveHeaders = (headers: Dom.RequestInit['headers']): Record = {} if (headers) { if ( - (typeof Headers !== 'undefined' && headers instanceof Headers) || + (typeof Headers !== `undefined` && headers instanceof Headers) || (CrossFetch && CrossFetch.Headers && headers instanceof CrossFetch.Headers) ) { oHeaders = HeadersInstanceToPlainObject(headers) @@ -71,186 +74,147 @@ const resolveHeaders = (headers: Dom.RequestInit['headers']): Record str.replace(/([\s,]|#[^\n\r]+)+/g, ' ').trim() - -type TBuildGetQueryParams = - | { - query: string - variables: V | undefined - operationName: string | undefined - jsonSerializer: Dom.JsonSerializer - } - | { - query: string[] - variables: V[] | undefined - operationName: undefined - jsonSerializer: Dom.JsonSerializer - } +const cleanQuery = (str: string): string => str.replace(/([\s,]|#[^\n\r]+)+/g, ` `).trim() + +type BuildRequestConfigParamsBatch = { + query: string[] + variables: V[] | undefined + operationName: undefined + jsonSerializer: Dom.JsonSerializer +} + +type BuildRequestConfigParamsSingle = { + query: string + variables: V | undefined + operationName: string | undefined + jsonSerializer: Dom.JsonSerializer +} + +type BuildRequestConfigParams = BuildRequestConfigParamsSingle | BuildRequestConfigParamsBatch /** * Create query string for GraphQL request - * - * @param {object} param0 - - * - * @param {string|string[]} param0.query the GraphQL document or array of document if it's a batch request - * @param {string|undefined} param0.operationName the GraphQL operation name - * @param {any|any[]} param0.variables the GraphQL variables to use */ -const buildGetQueryParams = ({ - query, - variables, - operationName, - jsonSerializer, -}: TBuildGetQueryParams): string => { - if (!Array.isArray(query)) { - const search: string[] = [`query=${encodeURIComponent(queryCleanner(query))}`] - - if (variables) { - search.push(`variables=${encodeURIComponent(jsonSerializer.stringify(variables))}`) +const buildRequestConfig = (params: BuildRequestConfigParams): string => { + if (!Array.isArray(params.query)) { + const params_ = params as BuildRequestConfigParamsSingle + const search: string[] = [`query=${encodeURIComponent(cleanQuery(params_.query))}`] + + if (params.variables) { + search.push(`variables=${encodeURIComponent(params_.jsonSerializer.stringify(params_.variables))}`) } - if (operationName) { - search.push(`operationName=${encodeURIComponent(operationName)}`) + if (params_.operationName) { + search.push(`operationName=${encodeURIComponent(params_.operationName)}`) } - return search.join('&') + return search.join(`&`) } - if (typeof variables !== 'undefined' && !Array.isArray(variables)) { - throw new Error('Cannot create query with given variable type, array expected') + if (typeof params.variables !== `undefined` && !Array.isArray(params.variables)) { + throw new Error(`Cannot create query with given variable type, array expected`) } // Batch support - const payload = query.reduce<{ query: string; variables: string | undefined }[]>( - (accu, currentQuery, index) => { - accu.push({ - query: queryCleanner(currentQuery), - variables: variables ? jsonSerializer.stringify(variables[index]) : undefined, + const params_ = params as BuildRequestConfigParamsBatch + const payload = params.query.reduce<{ query: string; variables: string | undefined }[]>( + (acc, currentQuery, index) => { + acc.push({ + query: cleanQuery(currentQuery), + variables: params_.variables ? params_.jsonSerializer.stringify(params_.variables[index]) : undefined, }) - return accu + return acc }, [] ) - return `query=${encodeURIComponent(jsonSerializer.stringify(payload))}` + return `query=${encodeURIComponent(params_.jsonSerializer.stringify(payload))}` } -/** - * Fetch data using POST method - */ -const post = async ({ - url, - query, - variables, - operationName, - headers, - fetch, - fetchOptions, - middleware, -}: { +type Fetch = (url: string, config: Dom.RequestInit) => Promise + +interface RequestVerbParams { url: string query: string | string[] - fetch: any + fetch: Fetch fetchOptions: Dom.RequestInit variables?: V headers?: Dom.RequestInit['headers'] operationName?: string middleware?: RequestMiddleware -}) => { - const body = createRequestBody(query, variables, operationName, fetchOptions.jsonSerializer) - - let init: Dom.RequestInit = { - method: 'POST', - headers: { - ...(typeof body === 'string' ? { 'Content-Type': 'application/json' } : {}), - ...headers, - }, - body, - ...fetchOptions, - } - if (middleware) { - ;({ url, ...init } = await Promise.resolve(middleware({ ...init, url, operationName, variables }))) - } - return await fetch(url, init) } -/** - * Fetch data using GET method - */ -const get = async ({ - url, - query, - variables, - operationName, - headers, - fetch, - fetchOptions, - middleware, -}: { - url: string - query: string | string[] - fetch: any - fetchOptions: Dom.RequestInit - variables?: V - headers?: Dom.RequestInit['headers'] - operationName?: string - middleware?: RequestMiddleware -}) => { - const queryParams = buildGetQueryParams({ - query, - variables, - operationName, - jsonSerializer: fetchOptions.jsonSerializer, - } as TBuildGetQueryParams) - - let init: Dom.RequestInit = { - method: 'GET', - headers, - ...fetchOptions, - } - if (middleware) { - ;({ url, ...init } = await Promise.resolve(middleware({ ...init, url, operationName, variables }))) +const createHttpMethodFetcher = + (method: 'GET' | 'POST') => + async (params: RequestVerbParams) => { + const { url, query, variables, operationName, fetch, fetchOptions, middleware } = params + + const headers = { ...params.headers } + let queryParams = `` + let body = undefined + + if (method === `POST`) { + body = createRequestBody(query, variables, operationName, fetchOptions.jsonSerializer) + if (typeof body === `string`) { + // @ts-expect-error todo + headers[`Content-Type`] = `application/json` + } + } else { + // @ts-expect-error todo needs ADT for TS to understand the different states + queryParams = buildRequestConfig({ + query, + variables, + operationName, + jsonSerializer: fetchOptions.jsonSerializer ?? defaultJsonSerializer, + }) + } + + const init: Dom.RequestInit = { + method, + headers, + body, + ...fetchOptions, + } + + let urlResolved = url + let initResolved = init + if (middleware) { + const result = await Promise.resolve(middleware({ ...init, url, operationName, variables })) + const { url: urlNew, ...initNew } = result + urlResolved = urlNew + initResolved = initNew + } + if (queryParams) { + urlResolved = `${urlResolved}?${queryParams}` + } + return await fetch(urlResolved, initResolved) } - return await fetch(`${url}?${queryParams}`, init) -} /** * GraphQL Client. */ export class GraphQLClient { - constructor(private url: string, private readonly options: PatchedRequestInit = {}) {} + constructor(private url: string, public readonly requestConfig: RequestConfig = {}) {} /** * Send a GraphQL query to the server. */ - async rawRequest( - query: string, - variables?: V, - requestHeaders?: Dom.RequestInit['headers'] - ): Promise> - async rawRequest( - options: RawRequestOptions - ): Promise> - async rawRequest( - queryOrOptions: string | RawRequestOptions, - variables?: V, - requestHeaders?: Dom.RequestInit['headers'] - ): Promise> { + rawRequest: RawRequestMethod = async ( + ...args: RawRequestMethodArgs + ): Promise> => { + const [queryOrOptions, variables, requestHeaders] = args const rawRequestOptions = parseRawRequestArgs(queryOrOptions, variables, requestHeaders) - let { + const { headers, fetch = crossFetch, - method = 'POST', + method = `POST`, requestMiddleware, responseMiddleware, ...fetchOptions - } = this.options - let { url } = this + } = this.requestConfig + const { url } = this if (rawRequestOptions.signal !== undefined) { fetchOptions.signal = rawRequestOptions.signal } @@ -288,44 +252,34 @@ export class GraphQLClient { /** * Send a GraphQL document to the server. */ - request( + async request( document: RequestDocument | TypedDocumentNode, - ...variablesAndRequestHeaders: V extends Record // do we have explicitly no variables allowed? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] - : keyof RemoveIndex extends never // do we get an empty variables object? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] - : [variables: V, requestHeaders?: Dom.RequestInit['headers']] + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs ): Promise - async request(options: RequestOptions): Promise - async request( + async request(options: RequestOptions): Promise + async request( documentOrOptions: RequestDocument | TypedDocumentNode | RequestOptions, - ...variablesAndRequestHeaders: V extends Record // do we have explicitly no variables allowed? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] - : keyof RemoveIndex extends never // do we get an empty variables object? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] - : [variables: V, requestHeaders?: Dom.RequestInit['headers']] + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs ): Promise { const [variables, requestHeaders] = variablesAndRequestHeaders - // @ts-ignore const requestOptions = parseRequestArgs(documentOrOptions, variables, requestHeaders) - let { + const { headers, fetch = crossFetch, - method = 'POST', + method = `POST`, requestMiddleware, responseMiddleware, ...fetchOptions - } = this.options - let { url } = this + } = this.requestConfig + const { url } = this if (requestOptions.signal !== undefined) { fetchOptions.signal = requestOptions.signal } - // @ts-ignore const { query, operationName } = resolveRequestDocument(requestOptions.document) - return makeRequest({ + return makeRequest({ url, query, variables: requestOptions.variables, @@ -356,26 +310,18 @@ export class GraphQLClient { /** * Send GraphQL documents in batch to the server. */ - batchRequests( + batchRequests( documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise - batchRequests(options: BatchRequestsOptions): Promise - batchRequests( + batchRequests(options: BatchRequestsOptions): Promise + batchRequests( documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, requestHeaders?: Dom.RequestInit['headers'] ): Promise { const batchRequestOptions = parseBatchRequestArgs(documentsOrOptions, requestHeaders) + const { headers, ...fetchOptions } = this.requestConfig - let { - headers, - fetch = crossFetch, - method = 'POST', - requestMiddleware, - responseMiddleware, - ...fetchOptions - } = this.options - let { url } = this if (batchRequestOptions.signal !== undefined) { fetchOptions.signal = batchRequestOptions.signal } @@ -385,36 +331,37 @@ export class GraphQLClient { ) const variables = batchRequestOptions.documents.map(({ variables }) => variables) - return makeRequest({ - url, + return makeRequest({ + url: this.url, query: queries, + // @ts-expect-error TODO reconcile batch variables into system. variables, headers: { ...resolveHeaders(callOrIdentity(headers)), ...resolveHeaders(batchRequestOptions.requestHeaders), }, operationName: undefined, - fetch, - method, + fetch: this.requestConfig.fetch ?? crossFetch, + method: this.requestConfig.method || `POST`, fetchOptions, - middleware: requestMiddleware, + middleware: this.requestConfig.requestMiddleware, }) .then((response) => { - if (responseMiddleware) { - responseMiddleware(response) + if (this.requestConfig.responseMiddleware) { + this.requestConfig.responseMiddleware(response) } return response.data }) .catch((error) => { - if (responseMiddleware) { - responseMiddleware(error) + if (this.requestConfig.responseMiddleware) { + this.requestConfig.responseMiddleware(error) } throw error }) } setHeaders(headers: Dom.RequestInit['headers']): GraphQLClient { - this.options.headers = headers + this.requestConfig.headers = headers return this } @@ -422,14 +369,14 @@ export class GraphQLClient { * Attach a header to the client. All subsequent requests will have this header. */ setHeader(key: string, value: string): GraphQLClient { - const { headers } = this.options + const { headers } = this.requestConfig if (headers) { // todo what if headers is in nested array form... ? //@ts-ignore headers[key] = value } else { - this.options.headers = { [key]: value } + this.requestConfig.headers = { [key]: value } } return this @@ -444,87 +391,90 @@ export class GraphQLClient { } } -async function makeRequest({ - url, - query, - variables, - headers, - operationName, - fetch, - method = 'POST', - fetchOptions, - middleware, -}: { +const makeRequest = async (params: { url: string query: string | string[] variables?: V headers?: Dom.RequestInit['headers'] operationName?: string - fetch: any - method: string + fetch: Fetch + method?: HTTPMethodInput fetchOptions: Dom.RequestInit middleware?: RequestMiddleware -}): Promise> { - const fetcher = method.toUpperCase() === 'POST' ? post : get - const isBatchingQuery = Array.isArray(query) - - const response = await fetcher({ - url, - query, - variables, - operationName, - headers, - fetch, - fetchOptions, - middleware, - }) - const result = await getResult(response, fetchOptions.jsonSerializer) +}): Promise> => { + const { query, variables, fetchOptions } = params + const fetcher = createHttpMethodFetcher(uppercase(params.method ?? `post`)) + const isBatchingQuery = Array.isArray(params.query) + const response = await fetcher(params) + const result = await getResult(response, fetchOptions.jsonSerializer ?? defaultJsonSerializer) - const successfullyReceivedData = - isBatchingQuery && Array.isArray(result) ? !result.some(({ data }) => !data) : !!result.data + const successfullyReceivedData = Array.isArray(result) + ? !result.some(({ data }) => !data) + : Boolean(result.data) const successfullyPassedErrorPolicy = + Array.isArray(result) || !result.errors || (Array.isArray(result.errors) && !result.errors.length) || - fetchOptions.errorPolicy === 'all' || - fetchOptions.errorPolicy === 'ignore' + fetchOptions.errorPolicy === `all` || + fetchOptions.errorPolicy === `ignore` if (response.ok && successfullyPassedErrorPolicy && successfullyReceivedData) { - const { headers, status } = response - - const { errors, ...rest } = result - const data = fetchOptions.errorPolicy === 'ignore' ? rest : result + // @ts-expect-error TODO fixme + const { errors, ...rest } = Array.isArray(result) ? result : result + const data = fetchOptions.errorPolicy === `ignore` ? rest : result + const dataEnvelope = isBatchingQuery ? { data } : data + // @ts-expect-error TODO return { - ...(isBatchingQuery ? { data } : data), - headers, - status, + ...dataEnvelope, + headers: response.headers, + status: response.status, } } else { - const errorResult = typeof result === 'string' ? { error: result } : result + const errorResult = + typeof result === `string` + ? { + error: result, + } + : result throw new ClientError( + // @ts-expect-error TODO { ...errorResult, status: response.status, headers: response.headers }, { query, variables } ) } } +// prettier-ignore +interface RawRequestMethod { + (query: string, variables?: V, requestHeaders?: Dom.RequestInit['headers']): Promise> + (options: RawRequestOptions): Promise> +} + +// prettier-ignore +type RawRequestMethodArgs = + | [query: string, variables?: V, requestHeaders?: Dom.RequestInit['headers']] + | [RawRequestOptions] + +// prettier-ignore +interface RawRequest { + (url: string, query: string, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs): Promise> + (options: RawRequestExtendedOptions): Promise> +} + +// prettier-ignore +type RawRequestArgs = + | [options: RawRequestExtendedOptions, query?: string, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs] + | [url: string, query?: string, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs] + /** * Send a GraphQL Query to the GraphQL server for execution. */ -export async function rawRequest( - url: string, - query: string, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders -): Promise> -export async function rawRequest( - options: RawRequestExtendedOptions -): Promise> -export async function rawRequest( - urlOrOptions: string | RawRequestExtendedOptions, - query?: string, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders -): Promise> { +export const rawRequest: RawRequest = async ( + ...args: RawRequestArgs +): Promise> => { + const [urlOrOptions, query, ...variablesAndRequestHeaders] = args const requestOptions = parseRawRequestExtendedArgs(urlOrOptions, query, ...variablesAndRequestHeaders) const client = new GraphQLClient(requestOptions.url) return client.rawRequest({ @@ -566,20 +516,20 @@ export async function rawRequest( * await request('https://foo.bar/graphql', gql`...`) * ``` */ -export async function request( +export async function request( url: string, // @ts-ignore document: RequestDocument | TypedDocumentNode, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs ): Promise -export async function request( +export async function request( options: RequestExtendedOptions ): Promise -export async function request( +export async function request( urlOrOptions: string | RequestExtendedOptions, // @ts-ignore document?: RequestDocument | TypedDocumentNode, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs ): Promise { // @ts-ignore const requestOptions = parseRequestExtendedArgs(urlOrOptions, document, ...variablesAndRequestHeaders) @@ -623,52 +573,57 @@ export async function request( * await batchRequests('https://foo.bar/graphql', [{ query: gql`...` }]) * ``` */ -export async function batchRequests( +export async function batchRequests( url: string, documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise -export async function batchRequests( +export async function batchRequests( options: BatchRequestsExtendedOptions ): Promise -export async function batchRequests( +export async function batchRequests( urlOrOptions: string | BatchRequestsExtendedOptions, documents?: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseBatchRequestsExtendedArgs(urlOrOptions, documents, requestHeaders) - const client = new GraphQLClient(requestOptions.url) - return client.batchRequests({ ...requestOptions }) + const params = parseBatchRequestsExtendedArgs(urlOrOptions, documents, requestHeaders) + const client = new GraphQLClient(params.url) + return client.batchRequests(params) } export default request -/** - * todo - */ -async function getResult(response: Dom.Response, jsonSerializer = defaultJsonSerializer): Promise { +const getResult = async ( + response: Dom.Response, + jsonSerializer: Dom.JsonSerializer +): Promise< + | { data: object; errors: undefined }[] + | { data: object; errors: undefined } + | { data: undefined; errors: object } + | { data: undefined; errors: object[] } +> => { let contentType: string | undefined response.headers.forEach((value, key) => { - if (key.toLowerCase() === 'content-type') { + if (key.toLowerCase() === `content-type`) { contentType = value } }) if ( contentType && - (contentType.toLowerCase().startsWith('application/json') || - contentType.toLowerCase().startsWith('application/graphql+json') || - contentType.toLowerCase().startsWith('application/graphql-response+json')) + (contentType.toLowerCase().startsWith(`application/json`) || + contentType.toLowerCase().startsWith(`application/graphql+json`) || + contentType.toLowerCase().startsWith(`application/graphql-response+json`)) ) { - return jsonSerializer.parse(await response.text()) + return jsonSerializer.parse(await response.text()) as any } else { - return response.text() + return response.text() as any } } -function callOrIdentity(value: MaybeFunction) { - return typeof value === 'function' ? (value as () => T)() : value +const callOrIdentity = (value: MaybeFunction) => { + return typeof value === `function` ? (value as () => T)() : value } /** @@ -684,23 +639,12 @@ function callOrIdentity(value: MaybeFunction) { * * Several tools in the Node GraphQL ecosystem are hardcoded to specially treat any template tag named "gql". For example see this prettier issue: https://github.com/prettier/prettier/issues/4360. Using this template tag has no runtime effect beyond variable interpolation. */ -export function gql(chunks: TemplateStringsArray, ...variables: any[]): string { +export const gql = (chunks: TemplateStringsArray, ...variables: any[]): string => { return chunks.reduce( - (accumulator, chunk, index) => `${accumulator}${chunk}${index in variables ? variables[index] : ''}`, - '' + (accumulator, chunk, index) => `${accumulator}${chunk}${index in variables ? variables[index] : ``}`, + `` ) } -/** - * Convert Headers instance into regular object - */ -const HeadersInstanceToPlainObject = (headers: Dom.Response['headers']): Record => { - const o: any = {} - headers.forEach((v, k) => { - o[k] = v - }) - return o -} - export { GraphQLWebSocketClient } from './graphql-ws.js' export { resolveRequestDocument } from './resolveRequestDocument.js' diff --git a/src/parseArgs.ts b/src/parseArgs.ts index a16e5931d..74229a434 100644 --- a/src/parseArgs.ts +++ b/src/parseArgs.ts @@ -1,23 +1,22 @@ -import { +import type * as Dom from './types.dom.js' +import type { BatchRequestDocument, + BatchRequestsExtendedOptions, BatchRequestsOptions, + RawRequestExtendedOptions, RawRequestOptions, RequestDocument, - RequestOptions, - BatchRequestsExtendedOptions, - RawRequestExtendedOptions, RequestExtendedOptions, + RequestOptions, Variables, - RemoveIndex, - VariablesAndRequestHeaders, + VariablesAndRequestHeadersArgs, } from './types.js' -import * as Dom from './types.dom.js' -export function parseRequestArgs( +export const parseRequestArgs = ( documentOrOptions: RequestDocument | RequestOptions, variables?: V, requestHeaders?: Dom.RequestInit['headers'] -): RequestOptions { +): RequestOptions => { return (documentOrOptions as RequestOptions).document ? (documentOrOptions as RequestOptions) : ({ @@ -28,11 +27,11 @@ export function parseRequestArgs( } as unknown as RequestOptions) } -export function parseRawRequestArgs( +export const parseRawRequestArgs = ( queryOrOptions: string | RawRequestOptions, variables?: V, requestHeaders?: Dom.RequestInit['headers'] -): RawRequestOptions { +): RawRequestOptions => { return (queryOrOptions as RawRequestOptions).query ? (queryOrOptions as RawRequestOptions) : ({ @@ -43,10 +42,10 @@ export function parseRawRequestArgs( } as unknown as RawRequestOptions) } -export function parseBatchRequestArgs( +export const parseBatchRequestArgs = ( documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, requestHeaders?: Dom.RequestInit['headers'] -): BatchRequestsOptions { +): BatchRequestsOptions => { return (documentsOrOptions as BatchRequestsOptions).documents ? (documentsOrOptions as BatchRequestsOptions) : { @@ -56,11 +55,11 @@ export function parseBatchRequestArgs( } } -export function parseRequestExtendedArgs( +export const parseRequestExtendedArgs = ( urlOrOptions: string | RequestExtendedOptions, document?: RequestDocument, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders -): RequestExtendedOptions { + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs +): RequestExtendedOptions => { const [variables, requestHeaders] = variablesAndRequestHeaders return (urlOrOptions as RequestExtendedOptions).document ? (urlOrOptions as RequestExtendedOptions) @@ -73,11 +72,11 @@ export function parseRequestExtendedArgs( } as unknown as RequestExtendedOptions) } -export function parseRawRequestExtendedArgs( +export const parseRawRequestExtendedArgs = ( urlOrOptions: string | RawRequestExtendedOptions, query?: string, - ...variablesAndRequestHeaders: VariablesAndRequestHeaders -): RawRequestExtendedOptions { + ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs +): RawRequestExtendedOptions => { const [variables, requestHeaders] = variablesAndRequestHeaders return (urlOrOptions as RawRequestExtendedOptions).query ? (urlOrOptions as RawRequestExtendedOptions) @@ -90,11 +89,11 @@ export function parseRawRequestExtendedArgs( } as unknown as RawRequestExtendedOptions) } -export function parseBatchRequestsExtendedArgs( +export const parseBatchRequestsExtendedArgs = ( urlOrOptions: string | BatchRequestsExtendedOptions, documents?: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] -): BatchRequestsExtendedOptions { +): BatchRequestsExtendedOptions => { return (urlOrOptions as BatchRequestsExtendedOptions).documents ? (urlOrOptions as BatchRequestsExtendedOptions) : { diff --git a/src/resolveRequestDocument.ts b/src/resolveRequestDocument.ts index 9362d70ac..825702189 100644 --- a/src/resolveRequestDocument.ts +++ b/src/resolveRequestDocument.ts @@ -1,15 +1,16 @@ -import { DocumentNode, OperationDefinitionNode, parse, print } from 'graphql' -import { RequestDocument } from './types.js' +import type { RequestDocument } from './types.js' +import type { DocumentNode, OperationDefinitionNode } from 'graphql' +import { parse, print } from 'graphql' /** * helpers */ -function extractOperationName(document: DocumentNode): string | undefined { +const extractOperationName = (document: DocumentNode): string | undefined => { let operationName = undefined const operationDefinitions = document.definitions.filter( - (definition) => definition.kind === 'OperationDefinition' + (definition) => definition.kind === `OperationDefinition` ) as OperationDefinitionNode[] if (operationDefinitions.length === 1) { @@ -19,8 +20,10 @@ function extractOperationName(document: DocumentNode): string | undefined { return operationName } -export function resolveRequestDocument(document: RequestDocument): { query: string; operationName?: string } { - if (typeof document === 'string') { +export const resolveRequestDocument = ( + document: RequestDocument +): { query: string; operationName?: string } => { + if (typeof document === `string`) { let operationName = undefined try { diff --git a/src/types.ts b/src/types.ts index 8c7393d5b..55c13b303 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,22 +1,20 @@ -import { TypedDocumentNode } from '@graphql-typed-document-node/core' -import { DocumentNode } from 'graphql/language/ast.js' +import type { RemoveIndex } from './helpers.js' +import type * as Dom from './types.dom.js' +import type { TypedDocumentNode } from '@graphql-typed-document-node/core' import type { GraphQLError } from 'graphql/error/GraphQLError.js' -import * as Dom from './types.dom.js' +import type { DocumentNode } from 'graphql/language/ast.js' export type { GraphQLError } -export type Variables = { [key: string]: any } +export type Variables = Record +export type BatchVariables = (Record | undefined)[] -export type RemoveIndex = { - [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] -} - -export interface GraphQLResponse { +export interface GraphQLResponse { data?: T errors?: GraphQLError[] - extensions?: any + extensions?: unknown status: number - [key: string]: any + [key: string]: unknown } export interface GraphQLRequestContext { @@ -42,7 +40,7 @@ export class ClientError extends Error { this.request = request // this is needed as Safari doesn't support .captureStackTrace - if (typeof Error.captureStackTrace === 'function') { + if (typeof Error.captureStackTrace === `function`) { Error.captureStackTrace(this, ClientError) } } @@ -58,13 +56,16 @@ export type RequestDocument = string | DocumentNode export interface Response { data: T - extensions?: any + extensions?: unknown headers: Dom.Headers errors?: GraphQLError[] status: number } -export type PatchedRequestInit = Omit & { +export type HTTPMethodInput = 'GET' | 'POST' | 'get' | 'post' + +export type RequestConfig = Omit & { + method?: HTTPMethodInput headers?: MaybeFunction requestMiddleware?: RequestMiddleware responseMiddleware?: (response: Response | Error) => void @@ -85,7 +86,7 @@ export type RawRequestOptions = { ? { variables?: V } : { variables: V }) -export type RequestOptions = { +export type RequestOptions = { document: RequestDocument | TypedDocumentNode requestHeaders?: Dom.RequestInit['headers'] signal?: Dom.RequestInit['signal'] @@ -101,7 +102,7 @@ export type BatchRequestsOptions = { signal?: Dom.RequestInit['signal'] } -export type RequestExtendedOptions = { +export type RequestExtendedOptions = { url: string } & RequestOptions @@ -123,8 +124,10 @@ type RequestExtendedInit = Dom.RequestInit & { variables?: V } -export type VariablesAndRequestHeaders = V extends Record // do we have explicitly no variables allowed? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] +// prettier-ignore +export type VariablesAndRequestHeadersArgs = + V extends Record // do we have explicitly no variables allowed? + ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] : keyof RemoveIndex extends never // do we get an empty variables object? - ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] - : [variables: V, requestHeaders?: Dom.RequestInit['headers']] + ? [variables?: V, requestHeaders?: Dom.RequestInit['headers']] + : [variables: V, requestHeaders?: Dom.RequestInit['headers']] diff --git a/tests/__helpers.ts b/tests/__helpers.ts index 38a6662dc..055f8b508 100644 --- a/tests/__helpers.ts +++ b/tests/__helpers.ts @@ -1,10 +1,12 @@ import { ApolloServer } from 'apollo-server-express' import body from 'body-parser' -import express, { Application, Request } from 'express' +import type { Application, Request } from 'express' +import express from 'express' import getPort from 'get-port' -import { createServer, Server } from 'http' -import { JsonArray, JsonObject } from 'type-fest' -import { beforeAll, afterEach, afterAll, beforeEach } from 'vitest' +import type { Server } from 'http' +import { createServer } from 'http' +import type { JsonArray, JsonObject } from 'type-fest' +import { afterAll, afterEach, beforeAll, beforeEach } from 'vitest' type CapturedRequest = Pick @@ -43,7 +45,7 @@ type MockResult = { }[] } -export function setupTestServer(delay?: number): Context { +export function setupMockServer(delay?: number): Context { const ctx = {} as Context beforeAll(async () => { const port = await getPort() @@ -51,19 +53,19 @@ export function setupTestServer(d ctx.server.use(body.json()) ctx.nodeServer = createServer() ctx.nodeServer.listen({ port }) - ctx.nodeServer.on('request', ctx.server) + ctx.nodeServer.on(`request`, ctx.server) await new Promise((res) => { - ctx.nodeServer.once('listening', res) + ctx.nodeServer.once(`listening`, res) }) ctx.url = `http://localhost:${port}` ctx.res = (spec?: T): MockResult => { const requests: CapturedRequest[] = [] - ctx.server.use('*', async function mock(req, res) { + ctx.server.use(`*`, async function mock(req, res) { if (delay) { await sleep(delay) } - req.headers.host = 'DYNAMIC' + req.headers.host = `DYNAMIC` requests.push({ method: req.method, headers: req.headers, @@ -84,7 +86,7 @@ export function setupTestServer(d afterEach(() => { // https://stackoverflow.com/questions/10378690/remove-route-mappings-in-nodejs-express/28369539#28369539 ctx.server._router.stack.forEach((item: any, i: number) => { - if (item.name === 'mock') ctx.server._router.stack.splice(i, 1) + if (item.name === `mock`) ctx.server._router.stack.splice(i, 1) }) }) @@ -124,7 +126,7 @@ export function createApolloServerContext({ typeDefs, resolvers }: ApolloServerC beforeEach(async () => { ctx.server = await startApolloServer({ typeDefs, resolvers }) const address = ctx.server.address() - if (address && typeof address === 'object') { + if (address && typeof address === `object`) { ctx.url = `http://localhost:${address.port}/graphql` } }) diff --git a/tests/batching.test.ts b/tests/batching.test.ts index 11d27ba86..f96886d20 100644 --- a/tests/batching.test.ts +++ b/tests/batching.test.ts @@ -1,17 +1,17 @@ -import { expect, test } from 'vitest' import { batchRequests } from '../src/index.js' -import { setupTestServer, MockSpecBatch } from './__helpers.js' +import type { MockSpecBatch } from './__helpers.js' +import { setupMockServer } from './__helpers.js' +import { expect, test } from 'vitest' -const ctx = setupTestServer() +const mockServer = setupMockServer() -test('minimal double query', async () => { - const firstResult = { me: { id: 'some-id' } } - const secondResult = { me: { id: 'another-id' } } - const okq = ctx.res({ +test(`minimal double query`, async () => { + const firstResult = { me: { id: `some-id` } } + const secondResult = { me: { id: `another-id` } } + mockServer.res({ body: [{ data: firstResult }, { data: secondResult }], }) - - const [firstResponse, secondResponse] = await batchRequests(ctx.url, [ + const [firstResponse, secondResponse] = await batchRequests(mockServer.url, [ { document: `{ me { id } }` }, { document: `{ me { id } }` }, ]) @@ -20,12 +20,12 @@ test('minimal double query', async () => { expect(secondResponse.data).toEqual(secondResult) }) -test('basic error', async () => { - ctx.res({ +test(`basic error`, async () => { + mockServer.res({ body: [ { errors: { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -37,16 +37,16 @@ test('basic error', async () => { ], }) - await expect(batchRequests(ctx.url, [{ document: `x` }])).rejects.toMatchInlineSnapshot( + await expect(batchRequests(mockServer.url, [{ document: `x` }])).rejects.toMatchInlineSnapshot( `[Error: GraphQL Error (Code: 200): {"response":{"0":{"errors":{"message":"Syntax Error GraphQL request (1:1) Unexpected Name \\"x\\"\\n\\n1: x\\n ^\\n","locations":[{"line":1,"column":1}]}},"status":200,"headers":{}},"request":{"query":["x"],"variables":[null]}}]` ) }) -test('successful query with another which make an error', async () => { - const firstResult = { data: { me: { id: 'some-id' } } } +test(`successful query with another which make an error`, async () => { + const firstResult = { data: { me: { id: `some-id` } } } const secondResult = { errors: { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -56,12 +56,12 @@ test('successful query with another which make an error', async () => { }, } - ctx.res({ + mockServer.res({ body: [firstResult, secondResult], }) await expect( - batchRequests(ctx.url, [{ document: `{ me { id } }` }, { document: `x` }]) + batchRequests(mockServer.url, [{ document: `{ me { id } }` }, { document: `x` }]) ).rejects.toMatchInlineSnapshot( `[Error: GraphQL Error (Code: 200): {"response":{"0":{"data":{"me":{"id":"some-id"}}},"1":{"errors":{"message":"Syntax Error GraphQL request (1:1) Unexpected Name \\"x\\"\\n\\n1: x\\n ^\\n","locations":[{"line":1,"column":1}]}},"status":200,"headers":{}},"request":{"query":["{ me { id } }","x"],"variables":[null,null]}}]` ) diff --git a/tests/custom-fetch.test.ts b/tests/custom-fetch.test.ts index d87eef4c2..f5b11aaa7 100644 --- a/tests/custom-fetch.test.ts +++ b/tests/custom-fetch.test.ts @@ -1,11 +1,11 @@ import { GraphQLClient } from '../src/index.js' -import { setupTestServer } from './__helpers.js' +import { setupMockServer } from './__helpers.js' import fetch from 'cross-fetch' import { expect, test } from 'vitest' -const ctx = setupTestServer() +const ctx = setupMockServer() -test('with custom fetch', async () => { +test(`with custom fetch`, async () => { let touched = false // wrap fetch in a custom method const customFetch = function (input: RequestInfo, init?: RequestInit) { diff --git a/tests/document-node.test.ts b/tests/document-node.test.ts index c814f719e..08fa375cd 100644 --- a/tests/document-node.test.ts +++ b/tests/document-node.test.ts @@ -1,11 +1,11 @@ +import { request } from '../src/index.js' +import { setupMockServer } from './__helpers.js' import { gql } from 'graphql-tag' import { expect, it } from 'vitest' -import { request } from '../src/index.js' -import { setupTestServer } from './__helpers.js' -const ctx = setupTestServer() +const ctx = setupMockServer() -it('accepts graphql DocumentNode as alternative to raw string', async () => { +it(`accepts graphql DocumentNode as alternative to raw string`, async () => { const mock = ctx.res({ body: { data: { foo: 1 } } }) await request( ctx.url, diff --git a/tests/endpoint.test.ts b/tests/endpoint.test.ts index 97d0bce7b..e679aafae 100644 --- a/tests/endpoint.test.ts +++ b/tests/endpoint.test.ts @@ -1,12 +1,12 @@ -import { describe, expect, test } from 'vitest' import { GraphQLClient } from '../src/index.js' -import { setupTestServer } from './__helpers.js' +import { setupMockServer } from './__helpers.js' +import { describe, expect, test } from 'vitest' -const ctx_0 = setupTestServer() -const ctx_1 = setupTestServer() +const ctx_0 = setupMockServer() +const ctx_1 = setupMockServer() -describe('using class', () => { - test('.setEndpoint that send request to new server', async () => { +describe(`using class`, () => { + test(`.setEndpoint that send request to new server`, async () => { expect(ctx_0.url === ctx_1.url).toEqual(false) const client = new GraphQLClient(ctx_0.url) const mock_0 = ctx_0.res() diff --git a/tests/errorPolicy.test.ts b/tests/errorPolicy.test.ts index d046dd1fb..8f9bb48a4 100644 --- a/tests/errorPolicy.test.ts +++ b/tests/errorPolicy.test.ts @@ -1,10 +1,10 @@ -import { expect, test } from 'vitest' import { GraphQLClient } from '../src/index.js' -import { setupTestServer } from './__helpers.js' +import { setupMockServer } from './__helpers.js' +import { expect, test } from 'vitest' -const ctx = setupTestServer() +const ctx = setupMockServer() const errors = { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -13,7 +13,7 @@ const errors = { ], } -test('should throw error when error policy not set', async () => { +test(`should throw error when error policy not set`, async () => { ctx.res({ body: { data: {}, @@ -21,10 +21,10 @@ test('should throw error when error policy not set', async () => { }, }) - expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow('GraphQL Error') + expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) }) -test('should throw error when error policy set to "none"', async () => { +test(`should throw error when error policy set to "none"`, async () => { ctx.res({ body: { data: {}, @@ -32,10 +32,10 @@ test('should throw error when error policy set to "none"', async () => { }, }) - expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow('GraphQL Error') + expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) }) -test('should not throw error when error policy set to "ignore" and return only data', async () => { +test(`should not throw error when error policy set to "ignore" and return only data`, async () => { ctx.res({ body: { data: { test: {} }, @@ -43,13 +43,13 @@ test('should not throw error when error policy set to "ignore" and return only d }, }) - const res = await new GraphQLClient(ctx.url, { errorPolicy: 'ignore' }).rawRequest(`x`) + const res = await new GraphQLClient(ctx.url, { errorPolicy: `ignore` }).rawRequest(`x`) expect(res).toEqual(expect.objectContaining({ data: { test: {} } })) expect(res).toEqual(expect.not.objectContaining({ errors })) }) -test('should not throw error when error policy set to "all" and return both data and error', async () => { +test(`should not throw error when error policy set to "all" and return both data and error`, async () => { ctx.res({ body: { data: { test: {} }, @@ -57,7 +57,7 @@ test('should not throw error when error policy set to "all" and return both data }, }) - const res = await new GraphQLClient(ctx.url, { errorPolicy: 'all' }).rawRequest(`x`) + const res = await new GraphQLClient(ctx.url, { errorPolicy: `all` }).rawRequest(`x`) expect(res).toEqual(expect.objectContaining({ data: { test: {} }, errors })) }) diff --git a/tests/general.test.ts b/tests/general.test.ts index 3c66db63d..7e8ae52b7 100644 --- a/tests/general.test.ts +++ b/tests/general.test.ts @@ -1,17 +1,19 @@ -import { gql } from 'graphql-tag' import { GraphQLClient, rawRequest, request } from '../src/index.js' -import { setupTestServer } from './__helpers.js' -import * as Dom from '../src/types.dom.js' -import { beforeEach, describe, expect, it, test, Mock, vitest } from 'vitest' +import type * as Dom from '../src/types.dom.js' +import type { RequestConfig } from '../src/types.js' +import { setupMockServer } from './__helpers.js' +import { gql } from 'graphql-tag' +import type { Mock } from 'vitest' +import { beforeEach, describe, expect, it, test, vitest } from 'vitest' -const ctx = setupTestServer() +const ctx = setupMockServer() -test('minimal query', async () => { +test(`minimal query`, async () => { const { data } = ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -20,16 +22,16 @@ test('minimal query', async () => { expect(await request(ctx.url, `{ me { id } }`)).toEqual(data) }) -test('minimal raw query', async () => { +test(`minimal raw query`, async () => { const { extensions, data } = ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, extensions: { - version: '1', + version: `1`, }, }, }).spec.body! @@ -37,20 +39,20 @@ test('minimal raw query', async () => { expect(result).toEqual({ data, extensions, status: 200 }) }) -test('minimal raw query with response headers', async () => { +test(`minimal raw query with response headers`, async () => { const { headers: reqHeaders, body } = ctx.res({ headers: { - 'Content-Type': 'application/json', - 'X-Custom-Header': 'test-custom-header', + 'Content-Type': `application/json`, + 'X-Custom-Header': `test-custom-header`, }, body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, extensions: { - version: '1', + version: `1`, }, }, }).spec @@ -58,22 +60,22 @@ test('minimal raw query with response headers', async () => { const { headers, ...result } = await rawRequest(ctx.url, `{ me { id } }`) expect(result).toEqual({ ...body, status: 200 }) - expect(headers.get('X-Custom-Header')).toEqual(reqHeaders!['X-Custom-Header']) + expect(headers.get(`X-Custom-Header`)).toEqual(reqHeaders![`X-Custom-Header`]) }) -test('minimal raw query with response headers and new graphql content type', async () => { +test(`minimal raw query with response headers and new graphql content type`, async () => { const { headers: reqHeaders, body } = ctx.res({ headers: { - 'Content-Type': 'application/graphql+json', + 'Content-Type': `application/graphql+json`, }, body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, extensions: { - version: '1', + version: `1`, }, }, }).spec @@ -83,19 +85,19 @@ test('minimal raw query with response headers and new graphql content type', asy expect(result).toEqual({ ...body, status: 200 }) }) -test('minimal raw query with response headers and application/graphql-response+json response type', async () => { +test(`minimal raw query with response headers and application/graphql-response+json response type`, async () => { const { headers: reqHeaders, body } = ctx.res({ headers: { - 'Content-Type': 'application/graphql-response+json', + 'Content-Type': `application/graphql-response+json`, }, body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, extensions: { - version: '1', + version: `1`, }, }, }).spec @@ -105,13 +107,13 @@ test('minimal raw query with response headers and application/graphql-response+j expect(result).toEqual({ ...body, status: 200 }) }) -test('content-type with charset', async () => { +test(`content-type with charset`, async () => { const { data } = ctx.res({ // headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -120,11 +122,11 @@ test('content-type with charset', async () => { expect(await request(ctx.url, `{ me { id } }`)).toEqual(data) }) -test('basic error', async () => { +test(`basic error`, async () => { ctx.res({ body: { errors: { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -142,11 +144,11 @@ test('basic error', async () => { ) }) -test('basic error with raw request', async () => { +test(`basic error with raw request`, async () => { ctx.res({ body: { errors: { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -162,12 +164,12 @@ test('basic error with raw request', async () => { ) }) -describe('middleware', () => { +describe(`middleware`, () => { let client: GraphQLClient let requestMiddleware: Mock let responseMiddleware: Mock - describe('successful requests', () => { + describe(`successful requests`, () => { beforeEach(() => { ctx.res({ body: { @@ -185,7 +187,7 @@ describe('middleware', () => { }) }) - it('request', async () => { + it(`request`, async () => { const requestPromise = client.request<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) const res = await requestPromise @@ -193,23 +195,23 @@ describe('middleware', () => { expect(res.result).toBe(123) }) - it('rawRequest', async () => { + it(`rawRequest`, async () => { const requestPromise = client.rawRequest<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) await requestPromise expect(responseMiddleware).toBeCalledTimes(1) }) - it('batchRequests', async () => { + it(`batchRequests`, async () => { const requestPromise = client.batchRequests<{ result: number }>([{ document: `x` }]) expect(requestMiddleware).toBeCalledTimes(1) await requestPromise expect(responseMiddleware).toBeCalledTimes(1) }) - it('url changes', async () => { + it(`url changes`, async () => { requestMiddleware = vitest.fn((req) => ({ ...req, url: ctx.url })) - const _client = new GraphQLClient('https://graphql.org', { + const _client = new GraphQLClient(`https://graphql.org`, { requestMiddleware, }) const requestPromise = _client.request<{ result: number }>(`x`) @@ -219,7 +221,7 @@ describe('middleware', () => { }) }) - describe('async request middleware', () => { + describe(`async request middleware`, () => { beforeEach(() => { ctx.res({ body: { @@ -235,31 +237,31 @@ describe('middleware', () => { }) }) - it('request', async () => { + it(`request`, async () => { const requestPromise = client.request<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) await requestPromise }) - it('rawRequest', async () => { + it(`rawRequest`, async () => { const requestPromise = client.rawRequest<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) await requestPromise }) - it('batchRequests', async () => { + it(`batchRequests`, async () => { const requestPromise = client.batchRequests<{ result: number }>([{ document: `x` }]) expect(requestMiddleware).toBeCalledTimes(1) await requestPromise }) }) - describe('failed requests', () => { + describe(`failed requests`, () => { beforeEach(() => { ctx.res({ body: { errors: { - message: 'Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n', + message: `Syntax Error GraphQL request (1:1) Unexpected Name "x"\n\n1: x\n ^\n`, locations: [ { line: 1, @@ -278,21 +280,21 @@ describe('middleware', () => { }) }) - it('request', async () => { + it(`request`, async () => { const requestPromise = client.request<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) await expect(requestPromise).rejects.toThrowError() expect(responseMiddleware).toBeCalledTimes(1) }) - it('rawRequest', async () => { + it(`rawRequest`, async () => { const requestPromise = client.rawRequest<{ result: number }>(`x`) expect(requestMiddleware).toBeCalledTimes(1) await expect(requestPromise).rejects.toThrowError() expect(responseMiddleware).toBeCalledTimes(1) }) - it('batchRequests', async () => { + it(`batchRequests`, async () => { const requestPromise = client.batchRequests<{ result: number }>([{ document: `x` }]) expect(requestMiddleware).toBeCalledTimes(1) await expect(requestPromise).rejects.toThrowError() @@ -303,18 +305,18 @@ describe('middleware', () => { // todo needs to be tested in browser environment // the options under test here aren't used by node-fetch -test.skip('extra fetch options', async () => { +test.skip(`extra fetch options`, async () => { const options: RequestInit = { - credentials: 'include', - mode: 'cors', - cache: 'reload', + credentials: `include`, + mode: `cors`, + cache: `reload`, } const client = new GraphQLClient(ctx.url, options as any) const { requests } = ctx.res({ - body: { data: { test: 'test' } }, + body: { data: { test: `test` } }, }) - await client.request('{ test }') + await client.request(`{ test }`) expect(requests).toMatchInlineSnapshot(` Array [ Object { @@ -336,12 +338,12 @@ test.skip('extra fetch options', async () => { `) }) -test('case-insensitive content-type header for custom fetch', async () => { - const testData = { data: { test: 'test' } } +test(`case-insensitive content-type header for custom fetch`, async () => { + const testData = { data: { test: `test` } } const testResponseHeaders = new Map() - testResponseHeaders.set('ConTENT-type', 'apPliCatiON/JSON') + testResponseHeaders.set(`ConTENT-type`, `apPliCatiON/JSON`) - const options: Dom.RequestInit = { + const options: RequestConfig = { fetch: function (url: string) { return Promise.resolve({ headers: testResponseHeaders, @@ -360,13 +362,13 @@ test('case-insensitive content-type header for custom fetch', async () => { } const client = new GraphQLClient(ctx.url, options) - const result = await client.request('{ test }') + const result = await client.request(`{ test }`) expect(result).toEqual(testData.data) }) -describe('operationName parsing', () => { - it('should work for gql documents', async () => { +describe(`operationName parsing`, () => { + it(`should work for gql documents`, async () => { const mock = ctx.res({ body: { data: { foo: 1 } } }) await request( ctx.url, @@ -378,10 +380,10 @@ describe('operationName parsing', () => { ) const requestBody = mock.requests[0]?.body - expect(requestBody?.['operationName']).toEqual('myGqlOperation') + expect(requestBody?.[`operationName`]).toEqual(`myGqlOperation`) }) - it('should work for string documents', async () => { + it(`should work for string documents`, async () => { const mock = ctx.res({ body: { data: { foo: 1 } } }) await request( ctx.url, @@ -393,19 +395,19 @@ describe('operationName parsing', () => { ) const requestBody = mock.requests[0]?.body - expect(requestBody?.['operationName']).toEqual('myStringOperation') + expect(requestBody?.[`operationName`]).toEqual(`myStringOperation`) }) }) -test('should not throw error when errors property is an empty array (occured when using UltraGraphQL)', async () => { +test(`should not throw error when errors property is an empty array (occured when using UltraGraphQL)`, async () => { ctx.res({ body: { - data: { test: 'test' }, + data: { test: `test` }, errors: [], }, }) const res = await new GraphQLClient(ctx.url).request(`{ test }`) - expect(res).toEqual(expect.objectContaining({ test: 'test' })) + expect(res).toEqual(expect.objectContaining({ test: `test` })) }) diff --git a/tests/gql.test.ts b/tests/gql.test.ts index d03db0abe..58b0b43c3 100644 --- a/tests/gql.test.ts +++ b/tests/gql.test.ts @@ -1,12 +1,12 @@ +import { request } from '../src/index.js' +import { setupMockServer } from './__helpers.js' import { gql } from 'graphql-tag' import { describe, expect, it } from 'vitest' -import { request } from '../src/index.js' -import { setupTestServer } from './__helpers.js' -const ctx = setupTestServer() +const ctx = setupMockServer() -describe('gql', () => { - it('passthrough allowing benefits of tooling for gql template tag', async () => { +describe(`gql`, () => { + it(`passthrough allowing benefits of tooling for gql template tag`, async () => { const mock = ctx.res({ body: { data: { foo: 1 } } }) await request( ctx.url, diff --git a/tests/headers.test.ts b/tests/headers.test.ts index 8ffc14bab..184f364fe 100644 --- a/tests/headers.test.ts +++ b/tests/headers.test.ts @@ -1,130 +1,130 @@ -import * as CrossFetch from 'cross-fetch' -import * as Dom from '../src/types.dom.js' import { GraphQLClient, request } from '../src/index.js' -import { setupTestServer } from './__helpers.js' +import type * as Dom from '../src/types.dom.js' +import { setupMockServer } from './__helpers.js' +import * as CrossFetch from 'cross-fetch' import { describe, expect, test } from 'vitest' -const ctx = setupTestServer() +const ctx = setupMockServer() // Headers not defined globally in Node -const H = typeof Headers === 'undefined' ? CrossFetch.Headers : Headers +const H = typeof Headers === `undefined` ? CrossFetch.Headers : Headers -describe('using class', () => { - test('.setHeader() sets a header that get sent to server', async () => { +describe(`using class`, () => { + test(`.setHeader() sets a header that get sent to server`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeader('x-foo', 'bar') + client.setHeader(`x-foo`, `bar`) const mock = ctx.res() await client.request(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) }) - describe('.setHeaders() sets headers that get sent to the server', () => { - test('with headers instance', async () => { + describe(`.setHeaders() sets headers that get sent to the server`, () => { + test(`with headers instance`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeaders(new H({ 'x-foo': 'bar' })) + client.setHeaders(new H({ 'x-foo': `bar` })) const mock = ctx.res() await client.request(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) }) - test('with headers object', async () => { + test(`with headers object`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeaders({ 'x-foo': 'bar' }) + client.setHeaders({ 'x-foo': `bar` }) const mock = ctx.res() await client.request(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) }) - test('with header tuples', async () => { + test(`with header tuples`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeaders([['x-foo', 'bar']]) + client.setHeaders([[`x-foo`, `bar`]]) const mock = ctx.res() await client.request(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) }) }) - describe('custom header in the request', () => { + describe(`custom header in the request`, () => { describe.each([ - [new H({ 'x-request-foo': 'request-bar' })], - [{ 'x-request-foo': 'request-bar' }], - [[['x-request-foo', 'request-bar']]], - ])('request unique header with request', (headerCase: Dom.RequestInit['headers']) => { - test('with request method', async () => { + [new H({ 'x-request-foo': `request-bar` })], + [{ 'x-request-foo': `request-bar` }], + [[[`x-request-foo`, `request-bar`]]], + ])(`request unique header with request`, (headerCase: Dom.RequestInit['headers']) => { + test(`with request method`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeaders(new H({ 'x-foo': 'bar' })) + client.setHeaders(new H({ 'x-foo': `bar` })) const mock = ctx.res() await client.request(`{ me { id } }`, {}, headerCase) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') - expect(mock.requests[0]?.headers['x-request-foo']).toEqual('request-bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) + expect(mock.requests[0]?.headers[`x-request-foo`]).toEqual(`request-bar`) }) - test('with rawRequest method', async () => { + test(`with rawRequest method`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeaders(new H({ 'x-foo': 'bar' })) + client.setHeaders(new H({ 'x-foo': `bar` })) const mock = ctx.res() await client.rawRequest(`{ me { id } }`, {}, headerCase) - expect(mock.requests[0]?.headers['x-foo']).toEqual('bar') - expect(mock.requests[0]?.headers['x-request-foo']).toEqual('request-bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`bar`) + expect(mock.requests[0]?.headers[`x-request-foo`]).toEqual(`request-bar`) }) }) describe.each([ - [new H({ 'x-foo': 'request-bar' })], - [{ 'x-foo': 'request-bar' }], - [[['x-foo', 'request-bar']]], - ])('request header overriding the client header', (headerCase: Dom.RequestInit['headers']) => { - test('with request method', async () => { + [new H({ 'x-foo': `request-bar` })], + [{ 'x-foo': `request-bar` }], + [[[`x-foo`, `request-bar`]]], + ])(`request header overriding the client header`, (headerCase: Dom.RequestInit['headers']) => { + test(`with request method`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeader('x-foo', 'bar') + client.setHeader(`x-foo`, `bar`) const mock = ctx.res() await client.request(`{ me { id } }`, {}, headerCase) - expect(mock.requests[0]?.headers['x-foo']).toEqual('request-bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`request-bar`) }) - test('with rawRequest method', async () => { + test(`with rawRequest method`, async () => { const client = new GraphQLClient(ctx.url) - client.setHeader('x-foo', 'bar') + client.setHeader(`x-foo`, `bar`) const mock = ctx.res() await client.rawRequest(`{ me { id } }`, {}, headerCase) - expect(mock.requests[0]?.headers['x-foo']).toEqual('request-bar') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`request-bar`) }) }) - describe('gets fresh dynamic headers before each request', () => { - test('with request method', async () => { - const objectChangedThroughReference = { 'x-foo': 'old' } + describe(`gets fresh dynamic headers before each request`, () => { + test(`with request method`, async () => { + const objectChangedThroughReference = { 'x-foo': `old` } const client = new GraphQLClient(ctx.url, { headers: () => objectChangedThroughReference }) - objectChangedThroughReference['x-foo'] = 'new' + objectChangedThroughReference[`x-foo`] = `new` const mock = ctx.res() await client.request(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('new') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`new`) }) - test('with rawRequest method', async () => { - const objectChangedThroughReference = { 'x-foo': 'old' } + test(`with rawRequest method`, async () => { + const objectChangedThroughReference = { 'x-foo': `old` } const client = new GraphQLClient(ctx.url, { headers: () => objectChangedThroughReference }) - objectChangedThroughReference['x-foo'] = 'new' + objectChangedThroughReference[`x-foo`] = `new` const mock = ctx.res() await client.rawRequest(`{ me { id } }`) - expect(mock.requests[0]?.headers['x-foo']).toEqual('new') + expect(mock.requests[0]?.headers[`x-foo`]).toEqual(`new`) }) }) }) }) -describe('using request function', () => { +describe(`using request function`, () => { describe.each([ - [new H({ 'x-request-foo': 'request-bar' })], - [{ 'x-request-foo': 'request-bar' }], - [[['x-request-foo', 'request-bar']]], - ])('request unique header with request', (headerCase: Dom.RequestInit['headers']) => { - test('sets header', async () => { + [new H({ 'x-request-foo': `request-bar` })], + [{ 'x-request-foo': `request-bar` }], + [[[`x-request-foo`, `request-bar`]]], + ])(`request unique header with request`, (headerCase: Dom.RequestInit['headers']) => { + test(`sets header`, async () => { const mock = ctx.res() await request(ctx.url, `{ me { id } }`, {}, headerCase) - expect(mock.requests[0]?.headers['x-request-foo']).toEqual('request-bar') + expect(mock.requests[0]?.headers[`x-request-foo`]).toEqual(`request-bar`) }) }) }) diff --git a/tests/json-serializer.test.ts b/tests/json-serializer.test.ts index 48db0b763..fe7c2d37d 100644 --- a/tests/json-serializer.test.ts +++ b/tests/json-serializer.test.ts @@ -1,93 +1,93 @@ +import { GraphQLClient } from '../src/index.js' +import { setupMockServer } from './__helpers.js' import { createReadStream } from 'fs' import { join } from 'path' -import { GraphQLClient } from '../src/index.js' -import { setupTestServer } from './__helpers.js' -import * as Dom from '../src/types.dom.js' import { beforeEach, describe, expect, test, vitest } from 'vitest' -const ctx = setupTestServer() +const ctx = setupMockServer() -describe('jsonSerializer option', () => { - let serializer: Dom.JsonSerializer - const testData = { data: { test: { name: 'test' } } } - let fetch: any +const createMockSerializer = () => ({ + stringify: vitest.fn(JSON.stringify), + parse: vitest.fn(JSON.parse), +}) - beforeEach(() => { - serializer = { - stringify: vitest.fn(JSON.stringify), - parse: vitest.fn(JSON.parse), - } - fetch = (url: string) => - Promise.resolve({ - headers: new Map([['Content-Type', 'application/json; charset=utf-8']]), - data: testData, - text: function () { - return JSON.stringify(testData) - }, - ok: true, - status: 200, - url, - }) - }) +const testData = { data: { test: { name: `test` } } } - test('is used for parsing response body', async () => { - const options: Dom.RequestInit = { jsonSerializer: serializer, fetch } - const client: GraphQLClient = new GraphQLClient(ctx.url, options) +const createMockFetch = () => (url: string) => + Promise.resolve({ + headers: new Map([[`Content-Type`, `application/json; charset=utf-8`]]), + data: testData, + text: () => { + return JSON.stringify(testData) + }, + ok: true, + status: 200, + url, + }) - const result = await client.request('{ test { name } }') +describe(`jsonSerializer option`, () => { + test(`is used for parsing response body`, async () => { + const client: GraphQLClient = new GraphQLClient(ctx.url, { + jsonSerializer: createMockSerializer(), + fetch: createMockFetch(), + }) + const result = await client.request(`{ test { name } }`) expect(result).toEqual(testData.data) - expect(serializer.parse).toBeCalledTimes(1) + expect(client.requestConfig.jsonSerializer?.parse).toBeCalledTimes(1) }) - describe('is used for serializing variables', () => { - const document = 'query getTest($name: String!) { test(name: $name) { name } }' - const simpleVariable = { name: 'test' } - - let options: Dom.RequestInit + describe(`is used for serializing variables`, () => { + const document = `query getTest($name: String!) { test(name: $name) { name } }` + const simpleVariable = { name: `test` } let client: GraphQLClient const testSingleQuery = (expectedNumStringifyCalls = 1, variables: any = simpleVariable) => async () => { await client.request(document, variables) - expect(serializer.stringify).toBeCalledTimes(expectedNumStringifyCalls) + expect(client.requestConfig.jsonSerializer?.stringify).toBeCalledTimes(expectedNumStringifyCalls) } const testBatchQuery = (expectedNumStringifyCalls: number, variables: any = simpleVariable) => async () => { await client.batchRequests([{ document, variables }]) - expect(serializer.stringify).toBeCalledTimes(expectedNumStringifyCalls) + expect(client.requestConfig.jsonSerializer?.stringify).toBeCalledTimes(expectedNumStringifyCalls) } - describe('request body', () => { + describe(`request body`, () => { beforeEach(() => { - options = { jsonSerializer: serializer, fetch } - client = new GraphQLClient(ctx.url, options) + client = new GraphQLClient(ctx.url, { + jsonSerializer: createMockSerializer(), + fetch: createMockFetch(), + }) }) - describe('without files', () => { - test('single query', testSingleQuery()) - test('batch query', testBatchQuery(1)) + describe(`without files`, () => { + test(`single query`, testSingleQuery()) + test(`batch query`, testBatchQuery(1)) }) - describe('with files', () => { - const fileName = 'signal.test.ts' + describe(`with files`, () => { + const fileName = `signal.test.ts` const file = createReadStream(join(__dirname, fileName)) - test('single query', testSingleQuery(2, { ...simpleVariable, file })) - test('batch query', testBatchQuery(2, { ...simpleVariable, file })) + test(`single query`, testSingleQuery(2, { ...simpleVariable, file })) + test(`batch query`, testBatchQuery(2, { ...simpleVariable, file })) }) }) - describe('query string', () => { + describe(`query string`, () => { beforeEach(() => { - options = { jsonSerializer: serializer, fetch, method: 'GET' } - client = new GraphQLClient(ctx.url, options) + client = new GraphQLClient(ctx.url, { + jsonSerializer: createMockSerializer(), + fetch: createMockFetch(), + method: `GET`, + }) }) - test('single query', testSingleQuery()) - test('batch query', testBatchQuery(2)) // once for variable and once for query batch array + test(`single query`, testSingleQuery()) + test(`batch query`, testBatchQuery(2)) // once for variable and once for query batch array }) }) }) diff --git a/tests/signal.test.ts b/tests/signal.test.ts index 3e0dcd478..5aeaacee0 100644 --- a/tests/signal.test.ts +++ b/tests/signal.test.ts @@ -1,10 +1,10 @@ import { batchRequests, GraphQLClient, rawRequest, request } from '../src/index.js' -import { setupTestServer, sleep } from './__helpers.js' +import { setupMockServer, sleep } from './__helpers.js' import { expect, it } from 'vitest' -const ctx = setupTestServer(20) +const ctx = setupMockServer(20) -it('should abort a request when the signal is defined in the GraphQLClient', async () => { +it(`should abort a request when the signal is defined in the GraphQLClient`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -12,19 +12,19 @@ it('should abort a request when the signal is defined in the GraphQLClient', asy const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) try { - await client.request('{ me { id } }') + await client.request(`{ me { id } }`) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a request when the signal is defined in GraphQLClient and after the request has been sent', async () => { +it(`should abort a request when the signal is defined in GraphQLClient and after the request has been sent`, async () => { const abortController = new AbortController() ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -33,8 +33,8 @@ it('should abort a request when the signal is defined in GraphQLClient and after expect.assertions(1) const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) - client.request('{ me { id } }').catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') + client.request(`{ me { id } }`).catch((error) => { + expect((error as Error).message).toEqual(`The user aborted a request.`) }) await sleep(10) @@ -42,7 +42,7 @@ it('should abort a request when the signal is defined in GraphQLClient and after await sleep(20) }) -it('should abort a raw request when the signal is defined in the GraphQLClient', async () => { +it(`should abort a raw request when the signal is defined in the GraphQLClient`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -52,11 +52,11 @@ it('should abort a raw request when the signal is defined in the GraphQLClient', try { await client.rawRequest(`{ me { id } }`) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort batch requests when the signal is defined in the GraphQLClient', async () => { +it(`should abort batch requests when the signal is defined in the GraphQLClient`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -66,11 +66,11 @@ it('should abort batch requests when the signal is defined in the GraphQLClient' try { await client.batchRequests([{ document: `{ me { id } }` }, { document: `{ me { id } }` }]) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a request when the signal overrides GraphQLClient settings', async () => { +it(`should abort a request when the signal overrides GraphQLClient settings`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -79,15 +79,15 @@ it('should abort a request when the signal overrides GraphQLClient settings', as try { await client.request({ - document: '{ me { id } }', + document: `{ me { id } }`, signal: abortController.signal, }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a raw request when the signal overrides GraphQLClient settings', async () => { +it(`should abort a raw request when the signal overrides GraphQLClient settings`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -95,13 +95,13 @@ it('should abort a raw request when the signal overrides GraphQLClient settings' const client = new GraphQLClient(ctx.url) try { - await client.rawRequest({ query: '{ me { id } }', signal: abortController.signal }) + await client.rawRequest({ query: `{ me { id } }`, signal: abortController.signal }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort batch requests when the signal overrides GraphQLClient settings', async () => { +it(`should abort batch requests when the signal overrides GraphQLClient settings`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -114,11 +114,11 @@ it('should abort batch requests when the signal overrides GraphQLClient settings signal: abortController.signal, }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a request', async () => { +it(`should abort a request`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -126,21 +126,21 @@ it('should abort a request', async () => { try { await request({ url: ctx.url, - document: '{ me { id } }', + document: `{ me { id } }`, signal: abortController.signal, }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a request after the request has been sent', async () => { +it(`should abort a request after the request has been sent`, async () => { const abortController = new AbortController() ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -150,10 +150,10 @@ it('should abort a request after the request has been sent', async () => { request({ url: ctx.url, - document: '{ me { id } }', + document: `{ me { id } }`, signal: abortController.signal, }).catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) }) await sleep(10) @@ -161,7 +161,7 @@ it('should abort a request after the request has been sent', async () => { await sleep(20) }) -it('should abort a raw request', async () => { +it(`should abort a raw request`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -169,21 +169,21 @@ it('should abort a raw request', async () => { try { await rawRequest({ url: ctx.url, - query: '{ me { id } }', + query: `{ me { id } }`, signal: abortController.signal, }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort a raw request after the request has been sent', async () => { +it(`should abort a raw request after the request has been sent`, async () => { const abortController = new AbortController() ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -193,10 +193,10 @@ it('should abort a raw request after the request has been sent', async () => { rawRequest({ url: ctx.url, - query: '{ me { id } }', + query: `{ me { id } }`, signal: abortController.signal, }).catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) }) await sleep(10) @@ -204,7 +204,7 @@ it('should abort a raw request after the request has been sent', async () => { await sleep(20) }) -it('should abort batch requests', async () => { +it(`should abort batch requests`, async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -216,17 +216,17 @@ it('should abort batch requests', async () => { signal: abortController.signal, }) } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) } }) -it('should abort batch requests after a request has been sent', async () => { +it(`should abort batch requests after a request has been sent`, async () => { const abortController = new AbortController() ctx.res({ body: { data: { me: { - id: 'some-id', + id: `some-id`, }, }, }, @@ -239,7 +239,7 @@ it('should abort batch requests after a request has been sent', async () => { documents: [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], signal: abortController.signal, }).catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') + expect((error as Error).message).toEqual(`The user aborted a request.`) }) await sleep(10) diff --git a/tests/typed-document-node.test.ts b/tests/typed-document-node.test.ts index 53b916f03..7a43e5ee1 100644 --- a/tests/typed-document-node.test.ts +++ b/tests/typed-document-node.test.ts @@ -1,12 +1,12 @@ -import { TypedDocumentNode } from '@graphql-typed-document-node/core' +import request from '../src/index.js' +import { setupMockServer } from './__helpers.js' +import type { TypedDocumentNode } from '@graphql-typed-document-node/core' import { parse } from 'graphql' import { expect, test } from 'vitest' -import request from '../src/index.js' -import { setupTestServer } from './__helpers.js' -const ctx = setupTestServer() +const ctx = setupMockServer() -test('typed-document-node code should TS compile with variables', async () => { +test(`typed-document-node code should TS compile with variables`, async () => { ctx.res({ body: { data: { foo: 1 } } }) const query: TypedDocumentNode<{ echo: string }, { str: string }> = parse(/* GraphQL */ ` @@ -22,7 +22,7 @@ test('typed-document-node code should TS compile with variables', async () => { // @ts-expect-error Arguments for the rest parameter '_variablesAndRequestHeaders' were not provided. await request(ctx.url, query) - await request(ctx.url, query, { str: 'Hi' }) + await request(ctx.url, query, { str: `Hi` }) // @ts-expect-error 'variables' is declared here. await request({ @@ -41,7 +41,7 @@ test('typed-document-node code should TS compile with variables', async () => { url: ctx.url, document: query, // @ts-expect-error Type '{ aaa: string; }' is not assignable to type '{ str: string; }'. - variables: { aaa: 'aaa' }, + variables: { aaa: `aaa` }, }) await request({ @@ -53,42 +53,42 @@ test('typed-document-node code should TS compile with variables', async () => { await request({ url: ctx.url, - document: 'a graphql query', - variables: { whatever: 'not typed' }, + document: `a graphql query`, + variables: { whatever: `not typed` }, }) await request<{ echo: string }, { str: string }>({ url: ctx.url, - document: 'a graphql query', - variables: { str: 'Hi' }, + document: `a graphql query`, + variables: { str: `Hi` }, }) await request<{ echo: string }, { str: string }>({ url: ctx.url, - document: 'a graphql query', + document: `a graphql query`, // @ts-expect-error Type 'number' is not assignable to type 'string'.ts(2322) variables: { str: 1 }, }) await request<{ echo: string }, { str: string }>({ url: ctx.url, - document: 'a graphql query', + document: `a graphql query`, // @ts-expect-error Type '{ aaa: string; }' is not assignable to type '{ str: string; }'. - variables: { aaa: 'aaa' }, + variables: { aaa: `aaa` }, }) await request({ url: ctx.url, document: query, variables: { - str: 'foo', + str: `foo`, }, }) expect(1).toBe(1) }) -test('typed-document-node code should TS compile without variables', async () => { +test(`typed-document-node code should TS compile without variables`, async () => { ctx.res({ body: { data: { foo: 1 } } }) const query: TypedDocumentNode<{ echo: string }> = parse(/* GraphQL */ ` @@ -115,13 +115,13 @@ test('typed-document-node code should TS compile without variables', async () => await request({ url: ctx.url, - document: 'a graphql query', + document: `a graphql query`, variables: {}, }) await request({ url: ctx.url, - document: 'a graphql query', + document: `a graphql query`, }) expect(1).toBe(1)