@biggora/claude-plugins 1.2.2 → 1.3.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 +5 -1
- package/package.json +1 -1
- package/registry/registry.json +15 -0
- package/specs/coding.md +11 -0
- package/src/commands/skills/add.js +115 -31
- package/src/commands/skills/list.js +25 -52
- package/src/commands/skills/remove.js +45 -27
- package/src/commands/skills/resolve.js +104 -0
- package/src/commands/skills/update.js +58 -74
- package/src/config.js +5 -0
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fundamentals-provider-scopes
|
|
3
|
+
description: Provider scopes in NestJS (singleton, request, transient)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Provider Scopes
|
|
7
|
+
|
|
8
|
+
Providers can have different lifetimes (scopes) that determine how instances are created and shared.
|
|
9
|
+
|
|
10
|
+
## Scope Types
|
|
11
|
+
|
|
12
|
+
- `DEFAULT` - Singleton, shared across entire application
|
|
13
|
+
- `REQUEST` - New instance per request
|
|
14
|
+
- `TRANSIENT` - New instance per consumer
|
|
15
|
+
|
|
16
|
+
## Default Scope (Singleton)
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
@Injectable()
|
|
20
|
+
export class CatsService {}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Singleton is the default and recommended scope.
|
|
24
|
+
|
|
25
|
+
## Request Scope
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { Injectable, Scope } from '@nestjs/common';
|
|
29
|
+
|
|
30
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
31
|
+
export class CatsService {}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Transient Scope
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
38
|
+
export class LoggerService {}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Custom Provider Scopes
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
{
|
|
45
|
+
provide: 'CACHE_MANAGER',
|
|
46
|
+
useClass: CacheManager,
|
|
47
|
+
scope: Scope.TRANSIENT,
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Controller Scope
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
@Controller({
|
|
55
|
+
path: 'cats',
|
|
56
|
+
scope: Scope.REQUEST,
|
|
57
|
+
})
|
|
58
|
+
export class CatsController {}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Accessing Request Object
|
|
62
|
+
|
|
63
|
+
Inject `REQUEST` to access request in request-scoped providers:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { Injectable, Scope, Inject } from '@nestjs/common';
|
|
67
|
+
import { REQUEST } from '@nestjs/core';
|
|
68
|
+
import { Request } from 'express';
|
|
69
|
+
|
|
70
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
71
|
+
export class CatsService {
|
|
72
|
+
constructor(@Inject(REQUEST) private request: Request) {}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Scope Hierarchy
|
|
77
|
+
|
|
78
|
+
Request scope bubbles up the injection chain. If a controller depends on a request-scoped provider, the controller becomes request-scoped.
|
|
79
|
+
|
|
80
|
+
## Key Points
|
|
81
|
+
|
|
82
|
+
- Singleton scope is default and recommended
|
|
83
|
+
- Request scope creates new instance per request
|
|
84
|
+
- Transient scope creates new instance per consumer
|
|
85
|
+
- Request scope propagates up dependency chain
|
|
86
|
+
- Use `REQUEST` token to access request object
|
|
87
|
+
- WebSocket gateways should not use request-scoped providers
|
|
88
|
+
|
|
89
|
+
<!--
|
|
90
|
+
Source references:
|
|
91
|
+
- https://docs.nestjs.com/fundamentals/injection-scopes
|
|
92
|
+
-->
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing
|
|
3
|
+
description: Unit testing and e2e testing in NestJS with @nestjs/testing
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testing
|
|
7
|
+
|
|
8
|
+
NestJS provides utilities for unit and end-to-end testing, with built-in Jest and Supertest integration.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm i --save-dev @nestjs/testing
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Unit Testing
|
|
17
|
+
|
|
18
|
+
### Basic Test Setup
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Test } from '@nestjs/testing';
|
|
22
|
+
import { CatsController } from './cats.controller';
|
|
23
|
+
import { CatsService } from './cats.service';
|
|
24
|
+
|
|
25
|
+
describe('CatsController', () => {
|
|
26
|
+
let controller: CatsController;
|
|
27
|
+
let service: CatsService;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
const moduleRef = await Test.createTestingModule({
|
|
31
|
+
controllers: [CatsController],
|
|
32
|
+
providers: [CatsService],
|
|
33
|
+
}).compile();
|
|
34
|
+
|
|
35
|
+
service = moduleRef.get(CatsService);
|
|
36
|
+
controller = moduleRef.get(CatsController);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return cats', async () => {
|
|
40
|
+
const result = ['cat'];
|
|
41
|
+
jest.spyOn(service, 'findAll').mockImplementation(() => result);
|
|
42
|
+
expect(await controller.findAll()).toBe(result);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Auto Mocking
|
|
48
|
+
|
|
49
|
+
Use `useMocker()` to automatically mock dependencies:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const moduleRef = await Test.createTestingModule({
|
|
53
|
+
controllers: [CatsController],
|
|
54
|
+
})
|
|
55
|
+
.useMocker((token) => {
|
|
56
|
+
if (token === CatsService) {
|
|
57
|
+
return { findAll: jest.fn().mockResolvedValue(['cat']) };
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.compile();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Overriding Providers
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const moduleRef = await Test.createTestingModule({
|
|
67
|
+
imports: [CatsModule],
|
|
68
|
+
})
|
|
69
|
+
.overrideProvider(CatsService)
|
|
70
|
+
.useValue(mockCatsService)
|
|
71
|
+
.compile();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## End-to-End Testing
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import * as request from 'supertest';
|
|
78
|
+
import { Test } from '@nestjs/testing';
|
|
79
|
+
import { CatsModule } from '../src/cats/cats.module';
|
|
80
|
+
import { INestApplication } from '@nestjs/common';
|
|
81
|
+
|
|
82
|
+
describe('Cats', () => {
|
|
83
|
+
let app: INestApplication;
|
|
84
|
+
|
|
85
|
+
beforeAll(async () => {
|
|
86
|
+
const moduleRef = await Test.createTestingModule({
|
|
87
|
+
imports: [CatsModule],
|
|
88
|
+
})
|
|
89
|
+
.overrideProvider(CatsService)
|
|
90
|
+
.useValue({ findAll: () => ['test'] })
|
|
91
|
+
.compile();
|
|
92
|
+
|
|
93
|
+
app = moduleRef.createNestApplication();
|
|
94
|
+
await app.init();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('/GET cats', () => {
|
|
98
|
+
return request(app.getHttpServer())
|
|
99
|
+
.get('/cats')
|
|
100
|
+
.expect(200)
|
|
101
|
+
.expect({ data: ['test'] });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterAll(async () => {
|
|
105
|
+
await app.close();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Testing Scoped Providers
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const contextId = ContextIdFactory.create();
|
|
114
|
+
jest.spyOn(ContextIdFactory, 'getByRequest').mockImplementation(() => contextId);
|
|
115
|
+
|
|
116
|
+
const service = await moduleRef.resolve(CatsService, contextId);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Override Methods
|
|
120
|
+
|
|
121
|
+
| Method | Purpose |
|
|
122
|
+
|--------|---------|
|
|
123
|
+
| `overrideProvider()` | Override a provider |
|
|
124
|
+
| `overrideModule()` | Override a module |
|
|
125
|
+
| `overrideGuard()` | Override a guard |
|
|
126
|
+
| `overrideInterceptor()` | Override an interceptor |
|
|
127
|
+
| `overrideFilter()` | Override an exception filter |
|
|
128
|
+
| `overridePipe()` | Override a pipe |
|
|
129
|
+
|
|
130
|
+
Each returns an object with `useClass`, `useValue`, or `useFactory`.
|
|
131
|
+
|
|
132
|
+
## Key Points
|
|
133
|
+
|
|
134
|
+
- Keep test files near source files with `.spec.ts` or `.test.ts` suffix
|
|
135
|
+
- E2E tests go in `test/` directory with `.e2e-spec.ts` suffix
|
|
136
|
+
- `compile()` is async and must be awaited
|
|
137
|
+
- Use `resolve()` for scoped providers instead of `get()`
|
|
138
|
+
|
|
139
|
+
<!--
|
|
140
|
+
Source references:
|
|
141
|
+
- https://docs.nestjs.com/fundamentals/testing
|
|
142
|
+
-->
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql
|
|
3
|
+
description: Building GraphQL APIs with code-first and schema-first approaches
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL
|
|
7
|
+
|
|
8
|
+
NestJS provides GraphQL integration through Apollo Server or Mercurius.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Apollo (default)
|
|
14
|
+
npm install @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/express5 graphql
|
|
15
|
+
|
|
16
|
+
# Mercurius (Fastify)
|
|
17
|
+
npm install @nestjs/graphql @nestjs/mercurius graphql mercurius
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Module } from '@nestjs/common';
|
|
24
|
+
import { GraphQLModule } from '@nestjs/graphql';
|
|
25
|
+
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
|
26
|
+
import { join } from 'path';
|
|
27
|
+
|
|
28
|
+
@Module({
|
|
29
|
+
imports: [
|
|
30
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
31
|
+
driver: ApolloDriver,
|
|
32
|
+
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
|
33
|
+
// Or in-memory: autoSchemaFile: true
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
export class AppModule {}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Code-First Approach
|
|
41
|
+
|
|
42
|
+
### Object Type
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
|
|
46
|
+
|
|
47
|
+
@ObjectType()
|
|
48
|
+
export class Author {
|
|
49
|
+
@Field(() => ID)
|
|
50
|
+
id: string;
|
|
51
|
+
|
|
52
|
+
@Field()
|
|
53
|
+
name: string;
|
|
54
|
+
|
|
55
|
+
@Field(() => Int, { nullable: true })
|
|
56
|
+
age?: number;
|
|
57
|
+
|
|
58
|
+
@Field(() => [Post])
|
|
59
|
+
posts: Post[];
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Resolver
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
|
|
67
|
+
|
|
68
|
+
@Resolver(() => Author)
|
|
69
|
+
export class AuthorsResolver {
|
|
70
|
+
constructor(private authorsService: AuthorsService) {}
|
|
71
|
+
|
|
72
|
+
@Query(() => [Author], { name: 'authors' })
|
|
73
|
+
findAll() {
|
|
74
|
+
return this.authorsService.findAll();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Query(() => Author, { name: 'author' })
|
|
78
|
+
findOne(@Args('id', { type: () => ID }) id: string) {
|
|
79
|
+
return this.authorsService.findOne(id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Mutation(() => Author)
|
|
83
|
+
createAuthor(@Args('input') input: CreateAuthorInput) {
|
|
84
|
+
return this.authorsService.create(input);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Input Type
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { InputType, Field } from '@nestjs/graphql';
|
|
93
|
+
|
|
94
|
+
@InputType()
|
|
95
|
+
export class CreateAuthorInput {
|
|
96
|
+
@Field()
|
|
97
|
+
name: string;
|
|
98
|
+
|
|
99
|
+
@Field({ nullable: true })
|
|
100
|
+
bio?: string;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Schema-First Approach
|
|
105
|
+
|
|
106
|
+
### GraphQL Schema
|
|
107
|
+
|
|
108
|
+
```graphql
|
|
109
|
+
# authors.graphql
|
|
110
|
+
type Author {
|
|
111
|
+
id: ID!
|
|
112
|
+
name: String!
|
|
113
|
+
posts: [Post!]!
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type Query {
|
|
117
|
+
authors: [Author!]!
|
|
118
|
+
author(id: ID!): Author
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Configuration
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
126
|
+
driver: ApolloDriver,
|
|
127
|
+
typePaths: ['./**/*.graphql'],
|
|
128
|
+
definitions: {
|
|
129
|
+
path: join(process.cwd(), 'src/graphql.ts'),
|
|
130
|
+
outputAs: 'class',
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Resolver
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
@Resolver('Author')
|
|
139
|
+
export class AuthorsResolver {
|
|
140
|
+
@Query('authors')
|
|
141
|
+
findAll() {
|
|
142
|
+
return this.authorsService.findAll();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@Query('author')
|
|
146
|
+
findOne(@Args('id') id: string) {
|
|
147
|
+
return this.authorsService.findOne(id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Subscriptions
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { Subscription, Resolver } from '@nestjs/graphql';
|
|
156
|
+
import { PubSub } from 'graphql-subscriptions';
|
|
157
|
+
|
|
158
|
+
const pubSub = new PubSub();
|
|
159
|
+
|
|
160
|
+
@Resolver()
|
|
161
|
+
export class AuthorsResolver {
|
|
162
|
+
@Mutation(() => Author)
|
|
163
|
+
async createAuthor(@Args('input') input: CreateAuthorInput) {
|
|
164
|
+
const author = await this.authorsService.create(input);
|
|
165
|
+
pubSub.publish('authorCreated', { authorCreated: author });
|
|
166
|
+
return author;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Subscription(() => Author)
|
|
170
|
+
authorCreated() {
|
|
171
|
+
return pubSub.asyncIterableIterator('authorCreated');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Field Resolvers
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
@Resolver(() => Author)
|
|
180
|
+
export class AuthorsResolver {
|
|
181
|
+
@ResolveField(() => [Post])
|
|
182
|
+
posts(@Parent() author: Author) {
|
|
183
|
+
return this.postsService.findByAuthorId(author.id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Guards and Interceptors
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
@Query(() => Author)
|
|
192
|
+
@UseGuards(GqlAuthGuard)
|
|
193
|
+
whoAmI(@CurrentUser() user: User) {
|
|
194
|
+
return user;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Context
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
202
|
+
driver: ApolloDriver,
|
|
203
|
+
autoSchemaFile: true,
|
|
204
|
+
context: ({ req, res }) => ({ req, res }),
|
|
205
|
+
}),
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Async Configuration
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
212
|
+
driver: ApolloDriver,
|
|
213
|
+
imports: [ConfigModule],
|
|
214
|
+
useFactory: async (configService: ConfigService) => ({
|
|
215
|
+
autoSchemaFile: true,
|
|
216
|
+
playground: configService.get('NODE_ENV') !== 'production',
|
|
217
|
+
}),
|
|
218
|
+
inject: [ConfigService],
|
|
219
|
+
}),
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Key Points
|
|
223
|
+
|
|
224
|
+
- **Code-first**: TypeScript classes generate GraphQL schema
|
|
225
|
+
- **Schema-first**: SDL files generate TypeScript types
|
|
226
|
+
- Use `autoSchemaFile` for code-first
|
|
227
|
+
- Use `typePaths` for schema-first
|
|
228
|
+
- Resolvers must be registered as providers
|
|
229
|
+
|
|
230
|
+
<!--
|
|
231
|
+
Source references:
|
|
232
|
+
- https://docs.nestjs.com/graphql/quick-start
|
|
233
|
+
-->
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql-resolvers-mutations
|
|
3
|
+
description: GraphQL resolvers, mutations, and field resolvers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL Resolvers & Mutations
|
|
7
|
+
|
|
8
|
+
## Object Types (Code First)
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
|
|
12
|
+
|
|
13
|
+
@ObjectType()
|
|
14
|
+
export class Author {
|
|
15
|
+
@Field(() => ID)
|
|
16
|
+
id: string;
|
|
17
|
+
|
|
18
|
+
@Field()
|
|
19
|
+
name: string;
|
|
20
|
+
|
|
21
|
+
@Field(() => Int, { nullable: true })
|
|
22
|
+
age?: number;
|
|
23
|
+
|
|
24
|
+
@Field(() => [Post])
|
|
25
|
+
posts: Post[];
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Resolver
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Resolver, Query, Mutation, Args, ResolveField, Parent, ID } from '@nestjs/graphql';
|
|
33
|
+
|
|
34
|
+
@Resolver(() => Author)
|
|
35
|
+
export class AuthorsResolver {
|
|
36
|
+
constructor(
|
|
37
|
+
private authorsService: AuthorsService,
|
|
38
|
+
private postsService: PostsService,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
@Query(() => [Author], { name: 'authors' })
|
|
42
|
+
findAll() {
|
|
43
|
+
return this.authorsService.findAll();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Query(() => Author, { nullable: true })
|
|
47
|
+
findOne(@Args('id', { type: () => ID }) id: string) {
|
|
48
|
+
return this.authorsService.findOne(id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@ResolveField(() => [Post])
|
|
52
|
+
posts(@Parent() author: Author) {
|
|
53
|
+
return this.postsService.findByAuthorId(author.id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Mutations
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { InputType, Field, ArgsType } from '@nestjs/graphql';
|
|
62
|
+
|
|
63
|
+
@InputType()
|
|
64
|
+
export class CreateAuthorInput {
|
|
65
|
+
@Field()
|
|
66
|
+
name: string;
|
|
67
|
+
|
|
68
|
+
@Field({ nullable: true })
|
|
69
|
+
bio?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Resolver(() => Author)
|
|
73
|
+
export class AuthorsResolver {
|
|
74
|
+
@Mutation(() => Author)
|
|
75
|
+
createAuthor(@Args('input') input: CreateAuthorInput) {
|
|
76
|
+
return this.authorsService.create(input);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Mutation(() => Author)
|
|
80
|
+
updateAuthor(
|
|
81
|
+
@Args('id', { type: () => ID }) id: string,
|
|
82
|
+
@Args('input') input: UpdateAuthorInput,
|
|
83
|
+
) {
|
|
84
|
+
return this.authorsService.update(id, input);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Mutation(() => Boolean)
|
|
88
|
+
deleteAuthor(@Args('id', { type: () => ID }) id: string) {
|
|
89
|
+
return this.authorsService.delete(id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Args Class
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
@ArgsType()
|
|
98
|
+
class GetAuthorsArgs {
|
|
99
|
+
@Field(() => Int, { defaultValue: 0 })
|
|
100
|
+
offset: number;
|
|
101
|
+
|
|
102
|
+
@Field(() => Int, { defaultValue: 10 })
|
|
103
|
+
limit: number;
|
|
104
|
+
|
|
105
|
+
@Field({ nullable: true })
|
|
106
|
+
search?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Query(() => [Author])
|
|
110
|
+
authors(@Args() args: GetAuthorsArgs) {
|
|
111
|
+
return this.authorsService.findAll(args);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Field Options
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
@Field(() => String, {
|
|
119
|
+
description: 'The author name',
|
|
120
|
+
deprecationReason: 'Use fullName instead',
|
|
121
|
+
nullable: true,
|
|
122
|
+
})
|
|
123
|
+
name?: string;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Nullable Arrays
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Array itself is nullable
|
|
130
|
+
@Field(() => [Post], { nullable: true })
|
|
131
|
+
posts?: Post[];
|
|
132
|
+
|
|
133
|
+
// Array items are nullable
|
|
134
|
+
@Field(() => [Post], { nullable: 'items' })
|
|
135
|
+
posts: (Post | null)[];
|
|
136
|
+
|
|
137
|
+
// Both nullable
|
|
138
|
+
@Field(() => [Post], { nullable: 'itemsAndList' })
|
|
139
|
+
posts?: (Post | null)[];
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## GraphQL Decorators
|
|
143
|
+
|
|
144
|
+
| Decorator | Purpose |
|
|
145
|
+
|-----------|---------|
|
|
146
|
+
| `@ObjectType()` | Define GraphQL type |
|
|
147
|
+
| `@Field()` | Define field |
|
|
148
|
+
| `@InputType()` | Define input type |
|
|
149
|
+
| `@ArgsType()` | Define args class |
|
|
150
|
+
| `@Resolver()` | Define resolver |
|
|
151
|
+
| `@Query()` | Define query |
|
|
152
|
+
| `@Mutation()` | Define mutation |
|
|
153
|
+
| `@ResolveField()` | Define field resolver |
|
|
154
|
+
| `@Parent()` | Access parent object |
|
|
155
|
+
| `@Args()` | Access arguments |
|
|
156
|
+
| `@Context()` | Access context |
|
|
157
|
+
|
|
158
|
+
## Query Options
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
@Query(() => Author, {
|
|
162
|
+
name: 'author', // GraphQL query name
|
|
163
|
+
description: 'Get author by ID',
|
|
164
|
+
nullable: true, // Can return null
|
|
165
|
+
deprecationReason: 'Use findAuthor instead',
|
|
166
|
+
})
|
|
167
|
+
async getAuthor(@Args('id', { type: () => ID }) id: string) {}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Context Access
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
@Query(() => Author)
|
|
174
|
+
whoAmI(@Context() context: any) {
|
|
175
|
+
return context.req.user;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Or extract specific property
|
|
179
|
+
@Query(() => Author)
|
|
180
|
+
whoAmI(@Context('req') req: Request) {
|
|
181
|
+
return req.user;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Using Guards
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
@Query(() => Author)
|
|
189
|
+
@UseGuards(GqlAuthGuard)
|
|
190
|
+
whoAmI(@Context() context: any) {
|
|
191
|
+
return context.req.user;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
<!--
|
|
196
|
+
Source references:
|
|
197
|
+
- https://docs.nestjs.com/graphql/resolvers-map
|
|
198
|
+
- https://docs.nestjs.com/graphql/mutations
|
|
199
|
+
-->
|