Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.

feat: add tada-output pre-processing (#273)

+5
.changeset/proud-taxis-rhyme.md
···
···
+
---
+
'@0no-co/graphqlsp': minor
+
---
+
+
Introduce option to pre-process the introspection file, this improves the performance of `gql.tada`. This will be enabled by default and can be turned off by leveraging `tadaDisablePreprocessing: true` in the `tsconfig`
+3 -1
README.md
···
you can opt into the object notation i.e. `{ "schema": { "url": "x", "headers": { "Authorization": "y" } }}`
**Optional**
- `template` add an additional template to the defaults `gql` and `graphql`
- `templateIsCallExpression` this tells our client that you are using `graphql('doc')` (default: true)
when using `false` it will look for tagged template literals
- `shouldCheckForColocatedFragments` when turned on, this will scan your imports to find
unused fragments and provide a message notifying you about them (only works with call-expressions, default: true)
- `trackFieldUsage` this only works with the client-preset, when turned on it will warn you about
-
unused fields within the same file. (only works with call-expressions, default: true)
- `tadaOutputLocation` when using `gql.tada` this can be convenient as it automatically generates
an `introspection.ts` file for you, just give it the directory to output to and you're done
## Tracking unused fields
···
you can opt into the object notation i.e. `{ "schema": { "url": "x", "headers": { "Authorization": "y" } }}`
**Optional**
+
- `template` add an additional template to the defaults `gql` and `graphql`
- `templateIsCallExpression` this tells our client that you are using `graphql('doc')` (default: true)
when using `false` it will look for tagged template literals
- `shouldCheckForColocatedFragments` when turned on, this will scan your imports to find
unused fragments and provide a message notifying you about them (only works with call-expressions, default: true)
- `trackFieldUsage` this only works with the client-preset, when turned on it will warn you about
+
unused fields within the same file. (only works with call-expressions, default: true)
- `tadaOutputLocation` when using `gql.tada` this can be convenient as it automatically generates
an `introspection.ts` file for you, just give it the directory to output to and you're done
+
- `tadaDisablePreprocessing` this setting disables the optimisation of `tadaOutput` to a pre-processed TypeScript type, this is off by default.
## Tracking unused fields
+38
packages/example-tada/introspection.d.ts
···
···
+
/* eslint-disable */
+
/* prettier-ignore */
+
+
/** An IntrospectionQuery representation of your schema.
+
*
+
* @remarks
+
* This is an introspection of your schema saved as a file by GraphQLSP.
+
* It will automatically be used by `gql.tada` to infer the types of your GraphQL documents.
+
* If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to
+
* instead save to a .ts instead of a .d.ts file.
+
*/
+
export type introspection = {
+
query: 'Query';
+
mutation: never;
+
subscription: never;
+
types: {
+
'Attack': { kind: 'OBJECT'; name: 'Attack'; fields: { 'damage': { name: 'damage'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; } }; }; };
+
'Int': unknown;
+
'String': unknown;
+
'AttacksConnection': { kind: 'OBJECT'; name: 'AttacksConnection'; fields: { 'fast': { name: 'fast'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; 'special': { name: 'special'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; }; };
+
'EvolutionRequirement': { kind: 'OBJECT'; name: 'EvolutionRequirement'; fields: { 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
+
'Pokemon': { kind: 'OBJECT'; name: 'Pokemon'; fields: { 'attacks': { name: 'attacks'; type: { kind: 'OBJECT'; name: 'AttacksConnection'; ofType: null; } }; 'classification': { name: 'classification'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'evolutionRequirements': { name: 'evolutionRequirements'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EvolutionRequirement'; ofType: null; }; } }; 'evolutions': { name: 'evolutions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; 'fleeRate': { name: 'fleeRate'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'height': { name: 'height'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'maxCP': { name: 'maxCP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxHP': { name: 'maxHP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'resistant': { name: 'resistant'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'types': { name: 'types'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weaknesses': { name: 'weaknesses'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weight': { name: 'weight'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; }; };
+
'Float': unknown;
+
'ID': unknown;
+
'PokemonDimension': { kind: 'OBJECT'; name: 'PokemonDimension'; fields: { 'maximum': { name: 'maximum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'minimum': { name: 'minimum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
+
'PokemonType': { kind: 'ENUM'; name: 'PokemonType'; type: 'Bug' | 'Dark' | 'Dragon' | 'Electric' | 'Fairy' | 'Fighting' | 'Fire' | 'Flying' | 'Ghost' | 'Grass' | 'Ground' | 'Ice' | 'Normal' | 'Poison' | 'Psychic' | 'Rock' | 'Steel' | 'Water'; };
+
'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'pokemon': { name: 'pokemon'; type: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; } }; 'pokemons': { name: 'pokemons'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; }; };
+
'Boolean': unknown;
+
};
+
};
+
+
import * as gqlTada from 'gql.tada';
+
+
declare module 'gql.tada' {
+
interface setupSchema {
+
introspection: introspection;
+
}
+
}
-448
packages/example-tada/introspection.ts
···
-
/* eslint-disable */
-
/* prettier-ignore */
-
-
/** An IntrospectionQuery representation of your schema.
-
*
-
* @remarks
-
* This is an introspection of your schema saved as a file by GraphQLSP.
-
* You may import it to create a `graphql()` tag function with `gql.tada`
-
* by importing it and passing it to `initGraphQLTada<>()`.
-
*
-
* @example
-
* ```
-
* import { initGraphQLTada } from 'gql.tada';
-
* import type { introspection } from './introspection';
-
*
-
* export const graphql = initGraphQLTada<{
-
* introspection: typeof introspection;
-
* scalars: {
-
* DateTime: string;
-
* Json: any;
-
* };
-
* }>();
-
* ```
-
*/
-
const introspection = {
-
"__schema": {
-
"queryType": {
-
"name": "Query"
-
},
-
"mutationType": null,
-
"subscriptionType": null,
-
"types": [
-
{
-
"kind": "OBJECT",
-
"name": "Attack",
-
"fields": [
-
{
-
"name": "damage",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "name",
-
"type": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "type",
-
"type": {
-
"kind": "ENUM",
-
"name": "PokemonType",
-
"ofType": null
-
},
-
"args": []
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "SCALAR",
-
"name": "Int"
-
},
-
{
-
"kind": "SCALAR",
-
"name": "String"
-
},
-
{
-
"kind": "OBJECT",
-
"name": "AttacksConnection",
-
"fields": [
-
{
-
"name": "fast",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "OBJECT",
-
"name": "Attack",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "special",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "OBJECT",
-
"name": "Attack",
-
"ofType": null
-
}
-
},
-
"args": []
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "OBJECT",
-
"name": "EvolutionRequirement",
-
"fields": [
-
{
-
"name": "amount",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "name",
-
"type": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
},
-
"args": []
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "OBJECT",
-
"name": "Pokemon",
-
"fields": [
-
{
-
"name": "attacks",
-
"type": {
-
"kind": "OBJECT",
-
"name": "AttacksConnection",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "classification",
-
"type": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "evolutionRequirements",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "OBJECT",
-
"name": "EvolutionRequirement",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "evolutions",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "OBJECT",
-
"name": "Pokemon",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "fleeRate",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Float",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "height",
-
"type": {
-
"kind": "OBJECT",
-
"name": "PokemonDimension",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "id",
-
"type": {
-
"kind": "NON_NULL",
-
"ofType": {
-
"kind": "SCALAR",
-
"name": "ID",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "maxCP",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "maxHP",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "name",
-
"type": {
-
"kind": "NON_NULL",
-
"ofType": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "resistant",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "ENUM",
-
"name": "PokemonType",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "types",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "ENUM",
-
"name": "PokemonType",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "weaknesses",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "ENUM",
-
"name": "PokemonType",
-
"ofType": null
-
}
-
},
-
"args": []
-
},
-
{
-
"name": "weight",
-
"type": {
-
"kind": "OBJECT",
-
"name": "PokemonDimension",
-
"ofType": null
-
},
-
"args": []
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "SCALAR",
-
"name": "Float"
-
},
-
{
-
"kind": "SCALAR",
-
"name": "ID"
-
},
-
{
-
"kind": "OBJECT",
-
"name": "PokemonDimension",
-
"fields": [
-
{
-
"name": "maximum",
-
"type": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
},
-
"args": []
-
},
-
{
-
"name": "minimum",
-
"type": {
-
"kind": "SCALAR",
-
"name": "String",
-
"ofType": null
-
},
-
"args": []
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "ENUM",
-
"name": "PokemonType",
-
"enumValues": [
-
{
-
"name": "Bug"
-
},
-
{
-
"name": "Dark"
-
},
-
{
-
"name": "Dragon"
-
},
-
{
-
"name": "Electric"
-
},
-
{
-
"name": "Fairy"
-
},
-
{
-
"name": "Fighting"
-
},
-
{
-
"name": "Fire"
-
},
-
{
-
"name": "Flying"
-
},
-
{
-
"name": "Ghost"
-
},
-
{
-
"name": "Grass"
-
},
-
{
-
"name": "Ground"
-
},
-
{
-
"name": "Ice"
-
},
-
{
-
"name": "Normal"
-
},
-
{
-
"name": "Poison"
-
},
-
{
-
"name": "Psychic"
-
},
-
{
-
"name": "Rock"
-
},
-
{
-
"name": "Steel"
-
},
-
{
-
"name": "Water"
-
}
-
]
-
},
-
{
-
"kind": "OBJECT",
-
"name": "Query",
-
"fields": [
-
{
-
"name": "pokemon",
-
"type": {
-
"kind": "OBJECT",
-
"name": "Pokemon",
-
"ofType": null
-
},
-
"args": [
-
{
-
"name": "id",
-
"type": {
-
"kind": "NON_NULL",
-
"ofType": {
-
"kind": "SCALAR",
-
"name": "ID",
-
"ofType": null
-
}
-
}
-
}
-
]
-
},
-
{
-
"name": "pokemons",
-
"type": {
-
"kind": "LIST",
-
"ofType": {
-
"kind": "OBJECT",
-
"name": "Pokemon",
-
"ofType": null
-
}
-
},
-
"args": [
-
{
-
"name": "limit",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
}
-
},
-
{
-
"name": "skip",
-
"type": {
-
"kind": "SCALAR",
-
"name": "Int",
-
"ofType": null
-
}
-
}
-
]
-
}
-
],
-
"interfaces": []
-
},
-
{
-
"kind": "SCALAR",
-
"name": "Boolean"
-
},
-
{
-
"kind": "SCALAR",
-
"name": "Any"
-
}
-
],
-
"directives": []
-
}
-
} as const;
-
-
export { introspection };
···
+1 -1
packages/example-tada/package.json
···
"license": "ISC",
"dependencies": {
"@graphql-typed-document-node/core": "^3.2.0",
-
"gql.tada": "^1.0.0",
"@urql/core": "^3.0.0",
"graphql": "^16.8.1",
"urql": "^4.0.6"
···
"license": "ISC",
"dependencies": {
"@graphql-typed-document-node/core": "^3.2.0",
+
"gql.tada": "^1.4.0",
"@urql/core": "^3.0.0",
"graphql": "^16.8.1",
"urql": "^4.0.6"
+2 -2
packages/example-tada/src/graphql.ts
···
import { initGraphQLTada } from 'gql.tada';
-
import type { introspection } from '../introspection';
export const graphql = initGraphQLTada<{
-
introspection: typeof introspection;
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
···
import { initGraphQLTada } from 'gql.tada';
+
import type { introspection } from '../introspection.d.ts';
export const graphql = initGraphQLTada<{
+
introspection: introspection;
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
+1 -1
packages/example-tada/tsconfig.json
···
{
"name": "@0no-co/graphqlsp",
"schema": "./schema.graphql",
-
"tadaOutputLocation": "./introspection.ts"
}
],
"jsx": "react-jsx",
···
{
"name": "@0no-co/graphqlsp",
"schema": "./schema.graphql",
+
"tadaOutputLocation": "./introspection.d.ts"
}
],
"jsx": "react-jsx",
+1
packages/graphqlsp/README.md
···
- `reservedKeys` this setting will affect `trackFieldUsage`, you can enter keys here that will be ignored
from usage tracking, so when they are unused in the component but used in i.e. the normalised cache you
won't get annoying warnings. (default `id`, `_id` and `__typename`, example: ['slug'])
## Tracking unused fields
···
- `reservedKeys` this setting will affect `trackFieldUsage`, you can enter keys here that will be ignored
from usage tracking, so when they are unused in the component but used in i.e. the normalised cache you
won't get annoying warnings. (default `id`, `_id` and `__typename`, example: ['slug'])
+
- `tadaDisablePreprocessing` this setting disables the optimisation of `tadaOutput` to a pre-processed TypeScript type, this is off by default.
## Tracking unused fields
+1 -3
packages/graphqlsp/package.json
···
"@sindresorhus/fnv1a": "^2.0.0",
"@types/node": "^18.15.11",
"@types/node-fetch": "^2.6.3",
-
"@urql/introspection": "^1.0.3",
"graphql": "^16.8.1",
"graphql-language-service": "^5.2.0",
"lru-cache": "^10.0.1",
-
"type-fest": "^4.11.1",
"typescript": "^5.3.3"
},
"dependencies": {
-
"json5": "^2.2.3",
"node-fetch": "^2.0.0"
},
"publishConfig": {
···
"@sindresorhus/fnv1a": "^2.0.0",
"@types/node": "^18.15.11",
"@types/node-fetch": "^2.6.3",
"graphql": "^16.8.1",
"graphql-language-service": "^5.2.0",
"lru-cache": "^10.0.1",
"typescript": "^5.3.3"
},
"dependencies": {
+
"@gql.tada/internal": "^0.1.0",
"node-fetch": "^2.0.0"
},
"publishConfig": {
+17 -101
packages/graphqlsp/src/graphql/getSchema.ts
···
introspectionFromSchema,
} from 'graphql';
import path from 'path';
-
import JSON5 from 'json5';
-
import { minifyIntrospectionQuery } from '@urql/introspection';
import fetch from 'node-fetch';
import fs from 'fs';
-
import type { TsConfigJson } from 'type-fest';
import { ts } from '../ts';
import { Logger } from '../index';
-
const preambleComments =
-
['/* eslint-disable */', '/* prettier-ignore */'].join('\n') + '\n';
-
-
const dtsAnnotationComment = [
-
'/** An IntrospectionQuery representation of your schema.',
-
' *',
-
' * @remarks',
-
' * This is an introspection of your schema saved as a file by GraphQLSP.',
-
' * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents.',
-
' * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to',
-
' * instead save to a .ts instead of a .d.ts file.',
-
' */',
-
].join('\n');
-
-
const tsAnnotationComment = [
-
'/** An IntrospectionQuery representation of your schema.',
-
' *',
-
' * @remarks',
-
' * This is an introspection of your schema saved as a file by GraphQLSP.',
-
' * You may import it to create a `graphql()` tag function with `gql.tada`',
-
' * by importing it and passing it to `initGraphQLTada<>()`.',
-
' *',
-
' * @example',
-
' * ```',
-
" * import { initGraphQLTada } from 'gql.tada';",
-
" * import type { introspection } from './introspection';",
-
' *',
-
' * export const graphql = initGraphQLTada<{',
-
' * introspection: typeof introspection;',
-
' * scalars: {',
-
' * DateTime: string;',
-
' * Json: any;',
-
' * };',
-
' * }>();',
-
' * ```',
-
' */',
-
].join('\n');
-
async function saveTadaIntrospection(
root: string,
schema: GraphQLSchema | IntrospectionQuery,
tadaOutputLocation: string,
logger: Logger
) {
const introspection = !('__schema' in schema)
? introspectionFromSchema(schema, { descriptions: false })
: schema;
-
const minified = minifyIntrospectionQuery(introspection, {
-
includeDirectives: false,
-
includeEnums: true,
-
includeInputs: true,
-
includeScalars: true,
});
-
-
const json = JSON.stringify(minified, null, 2);
let output = path.resolve(root, tadaOutputLocation);
let stat: fs.Stats | undefined;
-
let contents = '';
try {
stat = await fs.promises.stat(output);
···
return;
}
-
if (/\.d\.ts$/.test(output)) {
-
contents = [
-
preambleComments,
-
dtsAnnotationComment,
-
`export type introspection = ${json};\n`,
-
"import * as gqlTada from 'gql.tada';\n",
-
"declare module 'gql.tada' {",
-
' interface setupSchema {',
-
' introspection: introspection',
-
' }',
-
'}',
-
].join('\n');
-
} else if (path.extname(output) === '.ts') {
-
contents = [
-
preambleComments,
-
tsAnnotationComment,
-
`const introspection = ${json} as const;\n`,
-
'export { introspection };',
-
].join('\n');
-
} else {
-
logger(`Unknown file type on path @ ${output}`);
-
return;
-
}
-
await fs.promises.writeFile(output, contents);
logger(`Introspection saved to path @ ${output}`);
}
···
headers: Record<string, unknown>;
};
-
const getRootDir = (
-
info: ts.server.PluginCreateInfo,
-
tsconfigPath: string
-
): string | undefined => {
-
const tsconfigContents = info.project.readFile(tsconfigPath);
-
const parsed = JSON5.parse<TsConfigJson>(tsconfigContents!);
-
-
if (
-
parsed.compilerOptions?.plugins?.find(x => x.name === '@0no-co/graphqlsp')
-
) {
-
return path.dirname(tsconfigPath);
-
} else if (Array.isArray(parsed.extends)) {
-
return parsed.extends.find(p => {
-
const resolved = require.resolve(p, {
-
paths: [path.dirname(tsconfigPath)],
-
});
-
return getRootDir(info, resolved);
-
});
-
} else if (parsed.extends) {
-
const resolved = require.resolve(parsed.extends, {
-
paths: [path.dirname(tsconfigPath)],
-
});
-
return getRootDir(info, resolved);
-
}
-
};
-
export const loadSchema = (
info: ts.server.PluginCreateInfo,
schema: SchemaOrigin | string,
···
logger: Logger
): { current: GraphQLSchema | null; version: number } => {
const root =
-
getRootDir(info, info.project.getProjectName()) ||
-
path.dirname(info.project.getProjectName());
logger('Got root-directory to resolve schema from: ' + root);
const ref: {
current: GraphQLSchema | null;
···
root,
introspection,
tadaOutputLocation,
logger
);
}
···
root,
schemaOrIntrospection,
tadaOutputLocation,
logger
);
}
···
introspectionFromSchema,
} from 'graphql';
import path from 'path';
import fetch from 'node-fetch';
import fs from 'fs';
+
import {
+
resolveTypeScriptRootDir,
+
minifyIntrospection,
+
outputIntrospectionFile,
+
} from '@gql.tada/internal';
import { ts } from '../ts';
import { Logger } from '../index';
async function saveTadaIntrospection(
root: string,
schema: GraphQLSchema | IntrospectionQuery,
tadaOutputLocation: string,
+
disablePreprocessing: boolean,
logger: Logger
) {
const introspection = !('__schema' in schema)
? introspectionFromSchema(schema, { descriptions: false })
: schema;
+
const minified = minifyIntrospection(introspection);
+
+
const contents = await outputIntrospectionFile(minified, {
+
fileType: tadaOutputLocation,
+
shouldPreprocess: !disablePreprocessing,
});
let output = path.resolve(root, tadaOutputLocation);
let stat: fs.Stats | undefined;
try {
stat = await fs.promises.stat(output);
···
return;
}
await fs.promises.writeFile(output, contents);
logger(`Introspection saved to path @ ${output}`);
}
···
headers: Record<string, unknown>;
};
export const loadSchema = (
info: ts.server.PluginCreateInfo,
schema: SchemaOrigin | string,
···
logger: Logger
): { current: GraphQLSchema | null; version: number } => {
const root =
+
resolveTypeScriptRootDir(
+
path => info.project.readFile(path),
+
info.project.getProjectName()
+
) || path.dirname(info.project.getProjectName());
logger('Got root-directory to resolve schema from: ' + root);
const ref: {
current: GraphQLSchema | null;
···
root,
introspection,
tadaOutputLocation,
+
info.config.tadaDisablePreprocessing ?? false,
logger
);
}
···
root,
schemaOrIntrospection,
tadaOutputLocation,
+
info.config.tadaDisablePreprocessing ?? false,
logger
);
}
+1
packages/graphqlsp/src/index.ts
···
type Config = {
schema: SchemaOrigin | string;
templateIsCallExpression?: boolean;
shouldCheckForColocatedFragments?: boolean;
template?: string;
···
type Config = {
schema: SchemaOrigin | string;
+
tadaDisablePreprocessing?: boolean;
templateIsCallExpression?: boolean;
shouldCheckForColocatedFragments?: boolean;
template?: string;
+27 -29
pnpm-lock.yaml
···
specifier: ^3.0.0
version: 3.2.2(graphql@16.8.1)
gql.tada:
-
specifier: ^1.0.0
-
version: 1.0.0(graphql@16.8.1)
graphql:
specifier: ^16.8.1
version: 16.8.1
···
packages/graphqlsp:
dependencies:
-
json5:
-
specifier: ^2.2.3
-
version: 2.2.3
node-fetch:
specifier: ^2.0.0
version: 2.6.7
···
'@types/node-fetch':
specifier: ^2.6.3
version: 2.6.3
-
'@urql/introspection':
-
specifier: ^1.0.3
-
version: 1.0.3(graphql@16.8.1)
graphql:
specifier: ^16.8.1
version: 16.8.1
···
lru-cache:
specifier: ^10.0.1
version: 10.0.1
-
type-fest:
-
specifier: ^4.11.1
-
version: 4.11.1
typescript:
specifier: ^5.3.3
version: 5.3.3
···
dev: true
optional: true
/@graphql-codegen/add@5.0.0(graphql@16.8.1):
resolution: {integrity: sha512-ynWDOsK2yxtFHwcJTB9shoSkUd7YXd6ZE57f0nk7W5cu/nAgxZZpEsnTPEpZB/Mjf14YRGe2uJHQ7AfElHjqUQ==}
peerDependencies:
···
transitivePeerDependencies:
- graphql
dev: false
-
-
/@urql/introspection@1.0.3(graphql@16.8.1):
-
resolution: {integrity: sha512-5zgnfUDV10c3qudqYvfZ/rOtWVB2QvqanmoDMttqpt+TCCPkSUZdb2qcLCEB6DL7ph8mQRTZhXI29J57nTnqKg==}
-
peerDependencies:
-
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
-
dependencies:
-
graphql: 16.8.1
-
dev: true
/@vitest/expect@0.34.6:
resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==}
···
get-intrinsic: 1.2.2
dev: true
-
/gql.tada@1.0.0(graphql@16.8.1):
-
resolution: {integrity: sha512-bmZUHxXGXJNW8fPtXyWIwbLKbEjnKK3PF6XinZFK2PgcxSFJ/9p5fVDseE1xiCkf3nOA4aiSxY5OQ3xWNjCzvg==}
dependencies:
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
transitivePeerDependencies:
- graphql
dev: false
-
/gql.tada@1.2.1(graphql@16.8.1):
-
resolution: {integrity: sha512-Nx8x3g9WLT23eu9aL/4TTFDBwm7CBGVd4F2Jp2H5oOjDpuWv12i1mTLKReQwn2V1ZP+jG8V0ATXzFQZt1pxSgw==}
dependencies:
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
transitivePeerDependencies:
- graphql
dev: false
···
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
/jsonc-parser@3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
···
engines: {node: '>=10'}
dev: true
-
/type-fest@4.11.1:
-
resolution: {integrity: sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==}
-
engines: {node: '>=16'}
-
dev: true
-
/typed-array-length@1.0.4:
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
dependencies:
···
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
hasBin: true
-
dev: true
/ua-parser-js@0.7.37:
resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==}
···
resolution: {directory: packages/graphqlsp, type: directory}
name: '@0no-co/graphqlsp'
dependencies:
json5: 2.2.3
node-fetch: 2.6.7
transitivePeerDependencies:
···
specifier: ^3.0.0
version: 3.2.2(graphql@16.8.1)
gql.tada:
+
specifier: ^1.4.0
+
version: 1.4.0(graphql@16.8.1)
graphql:
specifier: ^16.8.1
version: 16.8.1
···
packages/graphqlsp:
dependencies:
+
'@gql.tada/internal':
+
specifier: ^0.1.0
+
version: 0.1.0
node-fetch:
specifier: ^2.0.0
version: 2.6.7
···
'@types/node-fetch':
specifier: ^2.6.3
version: 2.6.3
graphql:
specifier: ^16.8.1
version: 16.8.1
···
lru-cache:
specifier: ^10.0.1
version: 10.0.1
typescript:
specifier: ^5.3.3
version: 5.3.3
···
dev: true
optional: true
+
/@gql.tada/cli-utils@0.3.0:
+
resolution: {integrity: sha512-kDebLVuM5r3/bI1MmlhHr9VKHxXeq8Gxy1wHVTPva4R5ObfbhzxnHsTCvR6MUp8ziy9Pg9MESb8S1YZW8ohM3A==}
+
dependencies:
+
'@gql.tada/internal': 0.1.0
+
graphql: 16.8.1
+
dev: false
+
+
/@gql.tada/internal@0.1.0:
+
resolution: {integrity: sha512-FTvBVXVvt0xUo8hvRlwFoyeNXpUDqc+e20MzFkF8ozbsa5PoYb/gksmmnHMjUphsIq1H3Hq8o4RGstFN5LKH4w==}
+
dependencies:
+
graphql: 16.8.1
+
typescript: 5.3.3
+
/@graphql-codegen/add@5.0.0(graphql@16.8.1):
resolution: {integrity: sha512-ynWDOsK2yxtFHwcJTB9shoSkUd7YXd6ZE57f0nk7W5cu/nAgxZZpEsnTPEpZB/Mjf14YRGe2uJHQ7AfElHjqUQ==}
peerDependencies:
···
transitivePeerDependencies:
- graphql
dev: false
/@vitest/expect@0.34.6:
resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==}
···
get-intrinsic: 1.2.2
dev: true
+
/gql.tada@1.2.1(graphql@16.8.1):
+
resolution: {integrity: sha512-Nx8x3g9WLT23eu9aL/4TTFDBwm7CBGVd4F2Jp2H5oOjDpuWv12i1mTLKReQwn2V1ZP+jG8V0ATXzFQZt1pxSgw==}
dependencies:
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
transitivePeerDependencies:
- graphql
dev: false
+
/gql.tada@1.4.0(graphql@16.8.1):
+
resolution: {integrity: sha512-/LZJmInJQESn0QafOrDCJRk9ASeI65caU/HmarPtcSNitNWBrH7UfNOsHtISnTTA/CS80eUYqy3M4ogasFZWPQ==}
+
hasBin: true
dependencies:
'@0no-co/graphql.web': 1.0.4(graphql@16.8.1)
+
'@gql.tada/cli-utils': 0.3.0
+
'@gql.tada/internal': 0.1.0
transitivePeerDependencies:
- graphql
dev: false
···
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
+
dev: true
/jsonc-parser@3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
···
engines: {node: '>=10'}
dev: true
/typed-array-length@1.0.4:
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
dependencies:
···
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
hasBin: true
/ua-parser-js@0.7.37:
resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==}
···
resolution: {directory: packages/graphqlsp, type: directory}
name: '@0no-co/graphqlsp'
dependencies:
+
'@gql.tada/internal': 0.1.0
json5: 2.2.3
node-fetch: 2.6.7
transitivePeerDependencies: