@qubhq/eslint-plugin-nestjs-graphql 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -94
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
# eslint-plugin-nestjs-graphql
|
|
1
|
+
# @qubhq/eslint-plugin-nestjs-graphql
|
|
2
2
|
|
|
3
3
|
> **Note**
|
|
4
|
-
> This fork of `eslint-plugin-nestjs-graphql` extends the
|
|
5
|
-
> by adding support for
|
|
6
|
-
types
|
|
4
|
+
> This fork of [`eslint-plugin-nestjs-graphql`](https://github.com/Hatko/eslint-plugin-nestjs-graphql) extends the
|
|
5
|
+
> original functionality by adding support for selected [`graphql-scalars`](https://www.npmjs.com/package/graphql-scalars)
|
|
6
|
+
> types.
|
|
7
|
+
>
|
|
8
|
+
> Right now the following scalars are supported: `GraphQLUUID`. If you want more, open an issue or make a PR.
|
|
9
|
+
>
|
|
7
10
|
> Also adds support for `@typescript-eslint` v8
|
|
8
11
|
> Introduced new rule: `args-nullable-optional`
|
|
9
12
|
|
|
10
|
-
[](https://www.npmjs.com/package/@qubhq/eslint-plugin-nestjs-graphql)
|
|
14
|
+

|
|
15
|
+

|
|
16
|
+

|
|
17
|
+
[](https://qubhq.com)
|
|
11
18
|
|
|
12
|
-
This plugin intends to prevent issues with returning the wrong type from NestJS GraphQL resolvers. Relevant
|
|
19
|
+
This plugin intends to prevent issues with returning the wrong type from NestJS GraphQL resolvers. Relevant
|
|
20
|
+
to [Code first](https://docs.nestjs.com/graphql/quick-start#code-first) approach.
|
|
13
21
|
|
|
14
22
|
## Rules
|
|
15
23
|
|
|
@@ -23,18 +31,25 @@ The plugin supports rules:
|
|
|
23
31
|
|
|
24
32
|
### matching-return-type
|
|
25
33
|
|
|
26
|
-
When Code first approach is used, NestJS generates schema based on the decorators such as `ResolveField`, `Query`, or
|
|
34
|
+
When Code first approach is used, NestJS generates schema based on the decorators such as `ResolveField`, `Query`, or
|
|
35
|
+
`Mutation` which define the type of the returned value. However, the type of the returned value is not checked by
|
|
36
|
+
TypeScript compiler.
|
|
27
37
|
|
|
28
38
|
A query defined as:
|
|
29
39
|
|
|
30
40
|
```typescript
|
|
31
41
|
@Query(returns => Author)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
async
|
|
43
|
+
author(@Args('id', { type: () => Int })
|
|
44
|
+
id: number
|
|
45
|
+
)
|
|
46
|
+
{
|
|
47
|
+
return this.authorsService.findOneById(id);
|
|
48
|
+
}
|
|
35
49
|
```
|
|
36
50
|
|
|
37
|
-
can be implemented to return any type of value, e.g. `Promise<string>`. This will not be caught by TypeScript compiler,
|
|
51
|
+
can be implemented to return any type of value, e.g. `Promise<string>`. This will not be caught by TypeScript compiler,
|
|
52
|
+
but will result in runtime error when the GraphQL schema is generated.
|
|
38
53
|
|
|
39
54
|
This rule aims to solve this issue by checking the type of the returned value.
|
|
40
55
|
|
|
@@ -42,181 +57,244 @@ This rule aims to solve this issue by checking the type of the returned value.
|
|
|
42
57
|
|
|
43
58
|
```typescript
|
|
44
59
|
@Query(returns => Author)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
async
|
|
61
|
+
author(@Args('id', { type: () => Int })
|
|
62
|
+
id: number
|
|
63
|
+
):
|
|
64
|
+
Author
|
|
65
|
+
{
|
|
66
|
+
return this.authorsService.findOneById(id);
|
|
67
|
+
}
|
|
48
68
|
```
|
|
49
69
|
|
|
50
70
|
```typescript
|
|
51
71
|
@Query(returns => Author)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
async
|
|
73
|
+
author(@Args('id', { type: () => Int })
|
|
74
|
+
id: number
|
|
75
|
+
):
|
|
76
|
+
Promise < Author > {
|
|
77
|
+
return this.authorsService.findOneById(id);
|
|
78
|
+
}
|
|
55
79
|
```
|
|
56
80
|
|
|
57
81
|
```typescript
|
|
58
82
|
@Query(returns => [Author])
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
async
|
|
84
|
+
author(@Args('id', { type: () => Int })
|
|
85
|
+
id: number
|
|
86
|
+
):
|
|
87
|
+
Promise < Author[] > {
|
|
88
|
+
return this.authorsService.findOneById(id);
|
|
89
|
+
}
|
|
62
90
|
```
|
|
63
91
|
|
|
64
92
|
```typescript
|
|
65
93
|
@Query(returns => [Author], { nullable: true })
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
async
|
|
95
|
+
author(@Args('id', { type: () => Int })
|
|
96
|
+
id: number
|
|
97
|
+
):
|
|
98
|
+
Promise < Author[] | null > {
|
|
99
|
+
return this.authorsService.findOneById(id);
|
|
100
|
+
}
|
|
69
101
|
```
|
|
70
102
|
|
|
71
103
|
*Invalid*
|
|
72
104
|
|
|
73
105
|
```typescript
|
|
74
106
|
@Query(returns => Author)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
async
|
|
108
|
+
author(@Args('id', { type: () => Int })
|
|
109
|
+
id: number
|
|
110
|
+
):
|
|
111
|
+
string
|
|
112
|
+
{
|
|
113
|
+
return this.authorsService.findOneById(id);
|
|
114
|
+
}
|
|
78
115
|
```
|
|
79
116
|
|
|
80
117
|
```typescript
|
|
81
118
|
@Query(returns => Author)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
async
|
|
120
|
+
author(@Args('id', { type: () => Int })
|
|
121
|
+
id: number
|
|
122
|
+
):
|
|
123
|
+
Promise < Author | null > {
|
|
124
|
+
return this.authorsService.findOneById(id);
|
|
125
|
+
}
|
|
85
126
|
```
|
|
86
127
|
|
|
87
128
|
```typescript
|
|
88
129
|
@Query(returns => Author)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
130
|
+
async
|
|
131
|
+
author(@Args('id', { type: () => Int })
|
|
132
|
+
id: number
|
|
133
|
+
):
|
|
134
|
+
Promise < Author[] > {
|
|
135
|
+
return this.authorsService.findOneById(id);
|
|
136
|
+
}
|
|
92
137
|
```
|
|
93
138
|
|
|
94
139
|
### matching-resolve-field-parent-type
|
|
95
140
|
|
|
96
|
-
When resolving a field, the `@Parent()` decorator's type can mismatch the type returned from the `@Resolver()` decorator
|
|
141
|
+
When resolving a field, the `@Parent()` decorator's type can mismatch the type returned from the `@Resolver()` decorator
|
|
142
|
+
of the class. This may result in runtime error or unexpected behavior.
|
|
97
143
|
|
|
98
144
|
This rule aims to solve this issue by checking the type of the `@Parent` against `@Resolver()`.
|
|
99
145
|
|
|
100
146
|
*Valid*
|
|
101
147
|
|
|
102
148
|
```typescript
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
149
|
+
|
|
150
|
+
@Resolver(() => Author)
|
|
151
|
+
class AuthorResolver {
|
|
152
|
+
@ResolveField(() => [Book])
|
|
153
|
+
async books(@Parent() author: Author): Promise<Book[]> {
|
|
154
|
+
return this.booksService.findAllByAuthorId(author.id);
|
|
109
155
|
}
|
|
156
|
+
}
|
|
110
157
|
```
|
|
111
158
|
|
|
112
159
|
```typescript
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
160
|
+
|
|
161
|
+
@Resolver(Author)
|
|
162
|
+
class AuthorResolver {
|
|
163
|
+
@ResolveField(returns => [Book])
|
|
164
|
+
async books(@Parent() author: Author): Promise<Book[]> {
|
|
165
|
+
return this.booksService.findAllByAuthorId(author.id);
|
|
119
166
|
}
|
|
167
|
+
}
|
|
120
168
|
```
|
|
121
169
|
|
|
122
170
|
*Invalid*
|
|
123
171
|
|
|
124
172
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
173
|
+
|
|
174
|
+
@Resolver()
|
|
175
|
+
class AuthorResolver {
|
|
176
|
+
@ResolveField(returns => [Book])
|
|
177
|
+
async books(@Parent() author: Author): Promise<Book[]> {
|
|
178
|
+
return this.booksService.findAllByAuthorId(author.id);
|
|
131
179
|
}
|
|
180
|
+
}
|
|
132
181
|
```
|
|
133
182
|
|
|
134
183
|
```typescript
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
184
|
+
|
|
185
|
+
@Resolver(Author)
|
|
186
|
+
class AuthorResolver {
|
|
187
|
+
@ResolveField(returns => [Book])
|
|
188
|
+
async books(@Parent() author: Book): Promise<Book[]> {
|
|
189
|
+
return this.booksService.findAllByAuthorId(author.id);
|
|
141
190
|
}
|
|
191
|
+
}
|
|
142
192
|
```
|
|
143
193
|
|
|
144
194
|
### args-nullable-optional
|
|
145
195
|
|
|
146
|
-
When using the `@Args` decorator in NestJS GraphQL resolvers, there's a common mismatch between the `nullable` property
|
|
196
|
+
When using the `@Args` decorator in NestJS GraphQL resolvers, there's a common mismatch between the `nullable` property
|
|
197
|
+
in the decorator options and the optionality of the parameter in TypeScript. If an argument is marked as`nullable: true`
|
|
198
|
+
in GraphQL, it should be optional in TypeScript (using `?`), and vice versa.
|
|
147
199
|
|
|
148
|
-
This rule ensures consistency between GraphQL schema nullability and TypeScript parameter optionality to prevent runtime
|
|
200
|
+
This rule ensures consistency between GraphQL schema nullability and TypeScript parameter optionality to prevent runtime
|
|
201
|
+
errors and improve type safety.
|
|
149
202
|
|
|
150
203
|
*Valid*
|
|
151
204
|
|
|
152
205
|
```typescript
|
|
153
206
|
// Correct: nullable args with optional parameter
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
207
|
+
async
|
|
208
|
+
locations(
|
|
209
|
+
@BusinessId()
|
|
210
|
+
businessId: string,
|
|
211
|
+
@Args('input', { nullable: true })
|
|
212
|
+
input ? : LocationsQueryInput
|
|
213
|
+
):
|
|
214
|
+
Promise < LocationModel[] > {
|
|
215
|
+
return this.locationsService.getAllLocationsForBusiness(businessId, input)
|
|
216
|
+
}
|
|
160
217
|
```
|
|
161
218
|
|
|
162
219
|
```typescript
|
|
163
220
|
// Correct: required args with non-optional parameter
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
async
|
|
222
|
+
locations(
|
|
223
|
+
@BusinessId()
|
|
224
|
+
businessId: string,
|
|
225
|
+
@Args('input')
|
|
226
|
+
input: LocationsQueryInput
|
|
227
|
+
):
|
|
228
|
+
Promise < LocationModel[] > {
|
|
229
|
+
return this.locationsService.getAllLocationsForBusiness(businessId, input)
|
|
230
|
+
}
|
|
170
231
|
```
|
|
171
232
|
|
|
172
233
|
```typescript
|
|
173
234
|
// Correct: explicitly non-nullable args with non-optional parameter
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
235
|
+
async
|
|
236
|
+
locations(
|
|
237
|
+
@BusinessId()
|
|
238
|
+
businessId: string,
|
|
239
|
+
@Args('input', { nullable: false })
|
|
240
|
+
input: LocationsQueryInput
|
|
241
|
+
):
|
|
242
|
+
Promise < LocationModel[] > {
|
|
243
|
+
return this.locationsService.getAllLocationsForBusiness(businessId, input)
|
|
244
|
+
}
|
|
180
245
|
```
|
|
181
246
|
|
|
182
247
|
*Invalid*
|
|
183
248
|
|
|
184
249
|
```typescript
|
|
185
250
|
// Invalid: nullable args but parameter is not optional
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
251
|
+
async
|
|
252
|
+
locations(
|
|
253
|
+
@BusinessId()
|
|
254
|
+
businessId: string,
|
|
255
|
+
@Args('input', { nullable: true })
|
|
256
|
+
input: LocationsQueryInput
|
|
257
|
+
):
|
|
258
|
+
Promise < LocationModel[] > {
|
|
259
|
+
return this.locationsService.getAllLocationsForBusiness(businessId, input)
|
|
260
|
+
}
|
|
192
261
|
```
|
|
193
262
|
|
|
194
263
|
```typescript
|
|
195
264
|
// Invalid: optional parameter but args is not nullable
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
265
|
+
async
|
|
266
|
+
locations(
|
|
267
|
+
@BusinessId()
|
|
268
|
+
businessId: string,
|
|
269
|
+
@Args('input')
|
|
270
|
+
input ? : LocationsQueryInput
|
|
271
|
+
):
|
|
272
|
+
Promise < LocationModel[] > {
|
|
273
|
+
return this.locationsService.getAllLocationsForBusiness(businessId, input)
|
|
274
|
+
}
|
|
202
275
|
```
|
|
203
276
|
|
|
204
277
|
## Installation
|
|
205
278
|
|
|
206
279
|
```sh
|
|
207
280
|
# inside your project's working tree
|
|
208
|
-
npm i eslint-plugin-nestjs-graphql --save-dev
|
|
281
|
+
npm i @qubhq/eslint-plugin-nestjs-graphql --save-dev
|
|
209
282
|
```
|
|
210
283
|
|
|
211
284
|
The rules are off by default. To turn them on, add the following to your `.eslintrc` file:
|
|
212
285
|
|
|
213
286
|
```json
|
|
214
287
|
{
|
|
215
|
-
"plugins": [
|
|
288
|
+
"plugins": [
|
|
289
|
+
"@qubhq/nestjs-graphql"
|
|
290
|
+
],
|
|
216
291
|
"rules": {
|
|
217
|
-
"nestjs-graphql/matching-return-type": "error",
|
|
218
|
-
|
|
219
|
-
"nestjs-graphql/
|
|
292
|
+
"@qubhq/nestjs-graphql/matching-return-type": "error",
|
|
293
|
+
// `error` level is recommended
|
|
294
|
+
"@qubhq/nestjs-graphql/matching-resolve-field-parent-type": "error",
|
|
295
|
+
// `error` level is recommended
|
|
296
|
+
"@qubhq/nestjs-graphql/args-nullable-optional": "error"
|
|
297
|
+
// `error` level is recommended
|
|
220
298
|
}
|
|
221
299
|
}
|
|
222
300
|
```
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qubhq/eslint-plugin-nestjs-graphql",
|
|
3
|
-
"author": "
|
|
4
|
-
"
|
|
3
|
+
"author": "QubHQ",
|
|
4
|
+
"contributors": [
|
|
5
|
+
"Vladyslav Zavalykhatko (original author)"
|
|
6
|
+
],
|
|
7
|
+
"homepage": "https://github.com/qubhq/eslint-plugin-nestjs-graphql",
|
|
5
8
|
"repository": {
|
|
6
9
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/qubhq/eslint-plugin-nestjs-graphql.git"
|
|
8
11
|
},
|
|
9
12
|
"bugs": {
|
|
10
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/qubhq/eslint-plugin-nestjs-graphql/issues"
|
|
11
14
|
},
|
|
12
|
-
"version": "1.2.
|
|
15
|
+
"version": "1.2.1",
|
|
13
16
|
"description": "Ensure correct typing for NestJS GraphQL decorated methods",
|
|
14
17
|
"main": "./dist/index.js",
|
|
15
18
|
"files": [
|
|
@@ -25,12 +28,13 @@
|
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@types/estree": "^1.0.7",
|
|
27
30
|
"@types/node": "^22.15.18",
|
|
28
|
-
"@typescript-eslint/utils": "^8.32.1",
|
|
29
31
|
"@typescript-eslint/types": "^8.32.1",
|
|
32
|
+
"@typescript-eslint/utils": "^8.32.1",
|
|
33
|
+
"rimraf": "^6.0.1",
|
|
30
34
|
"typescript": "^5.8.3"
|
|
31
35
|
},
|
|
32
36
|
"scripts": {
|
|
33
37
|
"bump": "pnpx npm-check-updates -u --deep && pnpm i && pnpm upgrade",
|
|
34
|
-
"build": "pnpm tsc"
|
|
38
|
+
"build": "rimraf dist && pnpm tsc"
|
|
35
39
|
}
|
|
36
40
|
}
|