Skip to content

Commit

Permalink
Merge pull request #7 from rintoj/feat/reference-provider
Browse files Browse the repository at this point in the history
Feat/reference provider
  • Loading branch information
rintoj authored Jul 5, 2024
2 parents f962633 + a5f1795 commit cb394db
Show file tree
Hide file tree
Showing 15 changed files with 633 additions and 125 deletions.
176 changes: 96 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# GQL Assist

GQL Assist is a powerful tool designed to streamline the development of GraphQL APIs in a NestJS
environment. By automatically converting TypeScript classes, resolvers, and enums into their
corresponding GraphQL definitions, GQL Assist significantly reduces the amount of boilerplate code
you need to write.
GQL Assist is your go-to tool for supercharging GraphQL development. It simplifies writing GraphQL
queries for Apollo Client by converting them into TypeScript code, making your development process
smoother and error-free. On the server side, gql-assist streamlines API development in NestJS by
automatically converting TypeScript classes, resolvers, and enums into their GraphQL counterparts,
drastically reducing boilerplate code. With gql-assist, you can focus on building your application
while it handles the heavy lifting of GraphQL integration.

## Installation

Expand All @@ -21,21 +23,100 @@ yarn add gql-assist

## Features

GQL Assist provides several key functionalities:
Discover the power of GraphQL Assist with its suite of robust functionalities:

### React Hook

Transform your GraphQL queries into TypeScript code seamlessly compatible with
[`@apollo/client`](https://www.apollographql.com/docs/react/). With GraphQL Assist, writing GraphQL
queries for Apollo Client becomes a breeze, letting you concentrate on what matters most—building
your application.

### Server Side

Streamline your GraphQL API development in a [NestJS](https://docs.nestjs.com/graphql/quick-start)
environment with GraphQL Assist. Automatically convert TypeScript classes, resolvers, and enums into
their GraphQL definitions, slashing boilerplate code and boosting productivity.

- **Model Conversion**: Automatically converts TypeScript classes to NestJS GraphQL Object Types.
- **Resolver Conversion**: Automatically transforms resolver methods to GraphQL resolvers with
appropriate decorators.
- **Field Resolver Conversion**: Converts methods to GraphQL field resolvers with the necessary
decorators.
- **Enum Conversion**: Transforms TypeScript enums to GraphQL enums and registers them.
- **Model Conversion**: Instantly convert TypeScript classes to NestJS GraphQL Object Types for
generating models, inputs, and response types.
- **Resolver Conversion**: Effortlessly transform resolver methods into GraphQL resolvers with the
correct decorators.
- **Field Resolver Conversion**: Easily convert methods to GraphQL field resolvers with the
necessary decorators.
- **Enum Conversion**: Swiftly transform TypeScript enums to GraphQL enums and register them.

## Usage

```sh
npx gql-assist generate decorator
```

### React Hook

GraphQL Assist can also help you with writing queries for graphql client by converting GraphQL
queries into TypeScript code compatible with `@apollo/client`. With GraphQL Assist, writing GraphQL
queries for Apollo Client becomes easier and less error-prone, allowing you to focus more on
building your application.

```sh
npx gql-assist generate hook
```

#### Example

Given the following GraphQL query:

```ts
import gql from 'graphql-tag'

const query = gql`
query {
user {
name
}
}
```

GraphQL Assist will look at the schema and convert it to the following on save:

```ts
import { QueryHookOptions, useQuery } from '@apollo/client'
import gql from 'graphql-tag'
const query = gql`
query fetchUser($id: ID!) {
user(id: $id) {
name
}
}
`
export interface RequestType {
id: string | undefined
}
export interface QueryType {
user?: UserType
}
export interface UserType {
name?: string
__typename?: 'User'
}
export function useUserQuery(
request: RequestType,
options?: QueryHookOptions<QueryType, RequestType>,
) {
return useQuery<QueryType, RequestType>(query, {
variables: request,
skip: !request.id,
...options,
})
}
```

### Models

For GQL Assist to recognize and convert a TypeScript class into a GraphQL ObjectType, it should be
Expand Down Expand Up @@ -176,71 +257,6 @@ export enum UserStatus {
registerEnumType(UserStatus, { name: 'UserStatus' })
```

### React Hook

GraphQL Assist can also help you with writing queries for graphql client by converting GraphQL
queries into TypeScript code compatible with `@apollo/client`. With GraphQL Assist, writing GraphQL
queries for Apollo Client becomes easier and less error-prone, allowing you to focus more on
building your application.

```sh
npx gql-assist generate hook
```

#### Example

Given the following GraphQL query:

```ts
import gql from 'graphql-tag'

const query = gql`
query {
user {
name
}
}
```

GraphQL Assist will look at the schema and convert it to the following on save:

```ts
import { QueryHookOptions, useQuery } from '@apollo/client'
import gql from 'graphql-tag'
const query = gql`
query fetchUser($id: ID!) {
user(id: $id) {
name
}
}
`
export interface RequestType {
id: string | undefined
}
export interface QueryType {
user?: UserType
}
export interface UserType {
name?: string
__typename?: 'User'
}
export function useUserQuery(
request: RequestType,
options?: QueryHookOptions<QueryType, RequestType>,
) {
return useQuery<QueryType, RequestType>(query, {
variables: request,
skip: !request.id,
...options,
})
}
```

## Command: gql-assist

GQL Assist is a powerful tool designed to streamline the development of GraphQL APIs in a NestJS
Expand Down Expand Up @@ -346,14 +362,14 @@ name Name of the module
## VSCode Extension

For an enhanced development experience, you can install the
[GQL Assist](https://marketplace.visualstudio.com/items?itemName=rintoj.gql-assist) extension from
the Visual Studio Code Marketplace. This extension provides in-editor completions and suggestions,
making it even easier to work with GraphQL and NestJS.
[GraphQL Assist](https://marketplace.visualstudio.com/items?itemName=rintoj.gql-assist) extension
from the Visual Studio Code Marketplace. This extension provides in-editor completions and
suggestions, making it even easier to work with GraphQL and NestJS.

1. Open Visual Studio Code.
2. Go to the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of
the window or by pressing `Ctrl+Shift+X`.
3. Search for "GQL Assist".
3. Search for "GraphQL Assist".
4. Click the Install button to install the extension.
5. Once installed, the extension will provide code completions and suggestions directly within your
IDE.
Expand Down
22 changes: 1 addition & 21 deletions src/auto-complete/hook-auto-complete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as gql from 'graphql'
import { GQLAssistConfig, config } from '../config'
import { Position } from '../diff'
import { parseTSFile } from '../ts/parse-ts'
import { trimSpaces } from '../util/trim-spaces'
import { DEFAULT_SIPPET, autoCompleteHook } from './hook-auto-complete'

const schema = gql.buildSchema(`
Expand Down Expand Up @@ -50,27 +51,6 @@ const schema = gql.buildSchema(`
`)

function trimSpaces(content: string) {
const lines = content.split('\n')
const nonEmptyAt = lines.findIndex(line => line.trim() !== '') ?? ''
const firstNonEmptyLine = lines[nonEmptyAt]
const spaces = firstNonEmptyLine.split('').findIndex(i => i !== ' ')
if (spaces < 1) return content.slice(nonEmptyAt)
const output = lines.slice(nonEmptyAt).map(line =>
line.replace(
new RegExp(
new Array(spaces)
.fill(null)
.map(() => ' ')
.join(''),
),
'',
),
)
// console.log(output.map((l, i) => `${i}: ${l}`).join('\n'))
return output.join('\n')
}

async function autoComplete(
fileName: string,
content: string,
Expand Down
24 changes: 4 additions & 20 deletions src/auto-complete/hook-auto-complete.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import * as gql from 'graphql'
import { toNonNullArray } from 'tsds-tools'
import ts from 'typescript'
import { GQLAssistConfig } from '../config'
import { Position } from '../diff'
import { isFieldNode, isSelectionSetNode, makeQueryParsable } from '../gql'
import { getGQLNodeLocationRange } from '../gql/get-gql-node-location-range'
import { isFieldNode, makeQueryParsable } from '../gql'
import { getGQLNodeRange } from '../gql/get-gql-node-location-range'
import { isPositionWithInRange } from '../position'
import { getGQLContent, getGraphQLQueryVariable, getTSNodeLocationRange } from '../ts'
import { toNonNullArray } from 'tsds-tools'

export const DEFAULT_SIPPET = '{\n ${1}\n}'

function isInRange(node: gql.ASTNode, position: Position, offset?: Position) {
const nodeRange = getGQLNodeLocationRange(node, offset)
const nodeRange = getGQLNodeRange(node, offset)
return isPositionWithInRange(position, nodeRange)
}

Expand Down Expand Up @@ -54,22 +54,6 @@ function isEmptyQuery(query: string) {
return false
}

function isFieldName(field: gql.GraphQLField<any, any, any>, name: string) {
return field.name === name
}

function getFirstFieldByScalarType(objectType: gql.GraphQLObjectType, type: string) {
const fields = Object.values(objectType.getFields())
return fields.find(field => {
const nullableType = gql.getNullableType(field.type)
const isScalar = gql.isScalarType(nullableType)
if (isScalar) {
const namedType = gql.getNamedType(nullableType)
return namedType.name === type
}
})
}

export function autoCompleteHook(
sourceFile: ts.SourceFile,
position: Position,
Expand Down
98 changes: 98 additions & 0 deletions src/definition-provider/definition-provider-from-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Position, Range } from '../diff'
import { trimSpaces } from '../util/trim-spaces'
import { provideDefinitionFromSchema } from './definition-provider-from-schema'

const schema = `
type User {
id: ID!
name: String
address: Address
status: UserStatus
}
enum UserStatus {
ACTIVE,
DELETED
}
type Address {
id: String
address: String
city: City
}
type City {
name: String
code: String
}
"""
Tweet object
used as primitive type
"""
type Tweet {
tweetId: ID!
mentions: [User!]
}
type Query {
me: User
user(id: ID!): User
tweet(id: ID!): Tweet
}
type Mutation {
createUser(name: String): User
updateUser(id: ID!, name: String): User
}
type Subscription {
onUserChange(id: ID!): User
}
`

function getAt(schema: string, range: Range | null) {
if (!range) return
const lines = schema.split('\n').slice(range.start.line, range.end.line + 1)
return lines.join('\n')
}

describe('provideDefinitionFromSchema', () => {
test('should provide return address type', async () => {
const range = provideDefinitionFromSchema(schema, new Position(4, 16))
const output = getAt(schema, range)
expect(output).toEqual(
trimSpaces(`
type Address {
id: String
address: String
city: City
}`),
)
})

test('should provide user status', async () => {
const range = provideDefinitionFromSchema(schema, new Position(5, 16))
const output = getAt(schema, range)
expect(output).toEqual(
trimSpaces(`
enum UserStatus {
ACTIVE,
DELETED
}`),
)
})

test('should provide tweet type', async () => {
const range = provideDefinitionFromSchema(schema, new Position(36, 22))
const output = getAt(schema, range)
expect(output).toEqual(
trimSpaces(`
type Tweet {
tweetId: ID!
mentions: [User!]
}`),
)
})
})
Loading

0 comments on commit cb394db

Please sign in to comment.