@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,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql-scalars-unions-enums
|
|
3
|
+
description: GraphQL custom scalars, interfaces, union types, and enums
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL Scalars, Interfaces, Unions & Enums
|
|
7
|
+
|
|
8
|
+
## Interfaces
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { Field, ID, InterfaceType } from '@nestjs/graphql';
|
|
12
|
+
|
|
13
|
+
@InterfaceType()
|
|
14
|
+
export abstract class Character {
|
|
15
|
+
@Field(() => ID)
|
|
16
|
+
id: string;
|
|
17
|
+
|
|
18
|
+
@Field()
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Implement interface:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
@ObjectType({ implements: () => [Character] })
|
|
27
|
+
export class Human implements Character {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Custom `resolveType`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
@InterfaceType({
|
|
37
|
+
resolveType(book) {
|
|
38
|
+
if (book.colors) return ColoringBook;
|
|
39
|
+
return TextBook;
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
export abstract class Book { ... }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Interface resolver (shared field resolvers):
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
@Resolver(() => Character)
|
|
49
|
+
export class CharacterInterfaceResolver {
|
|
50
|
+
@ResolveField(() => [Character])
|
|
51
|
+
friends(@Parent() character) {
|
|
52
|
+
return this.getFriends(character);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Scalars, Unions & Enums
|
|
58
|
+
|
|
59
|
+
## Built-in Scalars
|
|
60
|
+
|
|
61
|
+
- `ID`, `Int`, `Float`, `String`, `Boolean`
|
|
62
|
+
- `GraphQLISODateTime` (default for Date)
|
|
63
|
+
- `GraphQLTimestamp` (Date as epoch ms)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
GraphQLModule.forRoot({
|
|
67
|
+
buildSchemaOptions: {
|
|
68
|
+
dateScalarMode: 'timestamp', // Use GraphQLTimestamp for Date
|
|
69
|
+
numberScalarMode: 'integer', // Use Int for number
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Custom Scalar
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { Scalar, CustomScalar } from '@nestjs/graphql';
|
|
78
|
+
import { Kind, ValueNode } from 'graphql';
|
|
79
|
+
|
|
80
|
+
@Scalar('Date', () => Date)
|
|
81
|
+
export class DateScalar implements CustomScalar<number, Date> {
|
|
82
|
+
description = 'Date custom scalar';
|
|
83
|
+
|
|
84
|
+
parseValue(value: number): Date {
|
|
85
|
+
return new Date(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
serialize(value: Date): number {
|
|
89
|
+
return value.getTime();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parseLiteral(ast: ValueNode): Date {
|
|
93
|
+
if (ast.kind === Kind.INT) return new Date(ast.value);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Register as provider. Use: `@Field(() => Date) creationDate: Date;`
|
|
100
|
+
|
|
101
|
+
## Import Scalar (e.g., JSON)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm i graphql-type-json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import GraphQLJSON from 'graphql-type-json';
|
|
109
|
+
|
|
110
|
+
GraphQLModule.forRoot({
|
|
111
|
+
resolvers: { JSON: GraphQLJSON },
|
|
112
|
+
}),
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Union Types
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createUnionType } from '@nestjs/graphql';
|
|
119
|
+
|
|
120
|
+
export const ResultUnion = createUnionType({
|
|
121
|
+
name: 'ResultUnion',
|
|
122
|
+
types: () => [Author, Book] as const,
|
|
123
|
+
resolveType(value) {
|
|
124
|
+
if (value.name) return Author;
|
|
125
|
+
if (value.title) return Book;
|
|
126
|
+
return null;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
@Query(() => [ResultUnion])
|
|
131
|
+
search(): Array<typeof ResultUnion> {
|
|
132
|
+
return [new Author(), new Book()];
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Return class instances for default `resolveType`. Schema first: add `__resolveType` resolver.
|
|
137
|
+
|
|
138
|
+
## Enums
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { registerEnumType } from '@nestjs/graphql';
|
|
142
|
+
|
|
143
|
+
export enum AllowedColor {
|
|
144
|
+
RED,
|
|
145
|
+
GREEN,
|
|
146
|
+
BLUE,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
registerEnumType(AllowedColor, {
|
|
150
|
+
name: 'AllowedColor',
|
|
151
|
+
description: 'Supported colors',
|
|
152
|
+
valuesMap: {
|
|
153
|
+
RED: { description: 'Default color' },
|
|
154
|
+
BLUE: { deprecationReason: 'Too blue' },
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
@Field(() => AllowedColor)
|
|
161
|
+
favoriteColor: AllowedColor;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Schema first: define in SDL; use `resolvers` for internal value mapping.
|
|
165
|
+
|
|
166
|
+
## Key Points
|
|
167
|
+
|
|
168
|
+
- Use abstract class with `@InterfaceType()` (not TypeScript interface)
|
|
169
|
+
- `implements: () => [Character]` for ObjectType implementing interface
|
|
170
|
+
- Use `as const` for union types array
|
|
171
|
+
- Custom scalars need `parseValue`, `serialize`, `parseLiteral`
|
|
172
|
+
- `graphql-scalars` package for common scalars (UUID, Email, etc.)
|
|
173
|
+
- Enum internal values: use resolver object in schema-first
|
|
174
|
+
|
|
175
|
+
<!--
|
|
176
|
+
Source references:
|
|
177
|
+
- https://docs.nestjs.com/graphql/interfaces
|
|
178
|
+
- https://docs.nestjs.com/graphql/scalars
|
|
179
|
+
- https://docs.nestjs.com/graphql/unions-and-enums
|
|
180
|
+
-->
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql-subscriptions
|
|
3
|
+
description: Real-time GraphQL subscriptions with PubSub
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL Subscriptions
|
|
7
|
+
|
|
8
|
+
Subscriptions enable real-time server-to-client communication.
|
|
9
|
+
|
|
10
|
+
## Enable Subscriptions (Apollo)
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
14
|
+
driver: ApolloDriver,
|
|
15
|
+
subscriptions: {
|
|
16
|
+
'graphql-ws': true,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic Subscription
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Resolver, Mutation, Subscription } from '@nestjs/graphql';
|
|
25
|
+
import { PubSub } from 'graphql-subscriptions';
|
|
26
|
+
|
|
27
|
+
const pubSub = new PubSub();
|
|
28
|
+
|
|
29
|
+
@Resolver(() => Comment)
|
|
30
|
+
export class CommentsResolver {
|
|
31
|
+
@Subscription(() => Comment)
|
|
32
|
+
commentAdded() {
|
|
33
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Mutation(() => Comment)
|
|
37
|
+
async addComment(@Args('input') input: CreateCommentInput) {
|
|
38
|
+
const comment = await this.commentsService.create(input);
|
|
39
|
+
|
|
40
|
+
// Publish event
|
|
41
|
+
pubSub.publish('commentAdded', { commentAdded: comment });
|
|
42
|
+
|
|
43
|
+
return comment;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Filtering Events
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
@Subscription(() => Comment, {
|
|
52
|
+
filter: (payload, variables) =>
|
|
53
|
+
payload.commentAdded.postId === variables.postId,
|
|
54
|
+
})
|
|
55
|
+
commentAdded(@Args('postId') postId: string) {
|
|
56
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Transforming Payload
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
@Subscription(() => Comment, {
|
|
64
|
+
resolve: (payload) => payload.commentAdded,
|
|
65
|
+
})
|
|
66
|
+
commentAdded() {
|
|
67
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Named Subscription
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
@Subscription(() => Comment, {
|
|
75
|
+
name: 'commentAdded',
|
|
76
|
+
})
|
|
77
|
+
subscribeToCommentAdded() {
|
|
78
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## PubSub as Provider
|
|
83
|
+
|
|
84
|
+
Inject PubSub instead of global instance:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// pubsub.module.ts
|
|
88
|
+
@Module({
|
|
89
|
+
providers: [
|
|
90
|
+
{
|
|
91
|
+
provide: 'PUB_SUB',
|
|
92
|
+
useValue: new PubSub(),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
exports: ['PUB_SUB'],
|
|
96
|
+
})
|
|
97
|
+
export class PubSubModule {}
|
|
98
|
+
|
|
99
|
+
// resolver
|
|
100
|
+
@Resolver()
|
|
101
|
+
export class CommentsResolver {
|
|
102
|
+
constructor(@Inject('PUB_SUB') private pubSub: PubSub) {}
|
|
103
|
+
|
|
104
|
+
@Subscription(() => Comment)
|
|
105
|
+
commentAdded() {
|
|
106
|
+
return this.pubSub.asyncIterableIterator('commentAdded');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Production PubSub
|
|
112
|
+
|
|
113
|
+
The default `PubSub` is not for production. Use Redis-backed implementation:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm install graphql-redis-subscriptions ioredis
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
|
121
|
+
import Redis from 'ioredis';
|
|
122
|
+
|
|
123
|
+
const options = {
|
|
124
|
+
host: 'localhost',
|
|
125
|
+
port: 6379,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
provide: 'PUB_SUB',
|
|
130
|
+
useFactory: () => new RedisPubSub({
|
|
131
|
+
publisher: new Redis(options),
|
|
132
|
+
subscriber: new Redis(options),
|
|
133
|
+
}),
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## WebSocket Authentication
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
141
|
+
driver: ApolloDriver,
|
|
142
|
+
subscriptions: {
|
|
143
|
+
'graphql-ws': {
|
|
144
|
+
onConnect: (context) => {
|
|
145
|
+
const { connectionParams, extra } = context;
|
|
146
|
+
const token = connectionParams.authToken;
|
|
147
|
+
|
|
148
|
+
if (!isValidToken(token)) {
|
|
149
|
+
throw new Error('Invalid token');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
extra.user = decodeToken(token);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
context: ({ extra }) => ({
|
|
157
|
+
user: extra?.user,
|
|
158
|
+
}),
|
|
159
|
+
}),
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Mercurius Subscriptions
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
GraphQLModule.forRoot<MercuriusDriverConfig>({
|
|
166
|
+
driver: MercuriusDriver,
|
|
167
|
+
subscription: true,
|
|
168
|
+
}),
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
@Subscription(() => Comment)
|
|
173
|
+
commentAdded(@Context('pubsub') pubSub: PubSub) {
|
|
174
|
+
return pubSub.subscribe('commentAdded');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@Mutation(() => Comment)
|
|
178
|
+
async addComment(
|
|
179
|
+
@Args('input') input: CreateCommentInput,
|
|
180
|
+
@Context('pubsub') pubSub: PubSub,
|
|
181
|
+
) {
|
|
182
|
+
const comment = await this.commentsService.create(input);
|
|
183
|
+
|
|
184
|
+
await pubSub.publish({
|
|
185
|
+
topic: 'commentAdded',
|
|
186
|
+
payload: { commentAdded: comment },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return comment;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Client-Side
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
import { createClient } from 'graphql-ws';
|
|
197
|
+
|
|
198
|
+
const client = createClient({
|
|
199
|
+
url: 'ws://localhost:3000/graphql',
|
|
200
|
+
connectionParams: {
|
|
201
|
+
authToken: 'Bearer xxx',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
client.subscribe(
|
|
206
|
+
{
|
|
207
|
+
query: `subscription { commentAdded { id content } }`,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
next: (data) => console.log(data),
|
|
211
|
+
error: (error) => console.error(error),
|
|
212
|
+
complete: () => console.log('done'),
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Key Points
|
|
218
|
+
|
|
219
|
+
- Use `graphql-ws` instead of deprecated `subscriptions-transport-ws`
|
|
220
|
+
- Payload shape must match subscription return type
|
|
221
|
+
- Use Redis-backed PubSub for production (multi-instance support)
|
|
222
|
+
- `filter` function determines which clients receive events
|
|
223
|
+
- `resolve` function transforms the payload before sending
|
|
224
|
+
|
|
225
|
+
<!--
|
|
226
|
+
Source references:
|
|
227
|
+
- https://docs.nestjs.com/graphql/subscriptions
|
|
228
|
+
-->
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microservices-grpc
|
|
3
|
+
description: gRPC microservices with Protocol Buffers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# gRPC Microservices
|
|
7
|
+
|
|
8
|
+
High-performance RPC framework using Protocol Buffers.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm i @grpc/grpc-js @grpc/proto-loader
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Server Setup
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
|
|
20
|
+
transport: Transport.GRPC,
|
|
21
|
+
options: {
|
|
22
|
+
package: 'hero',
|
|
23
|
+
protoPath: join(__dirname, 'hero/hero.proto'),
|
|
24
|
+
url: '0.0.0.0:50051',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Proto Definition
|
|
30
|
+
|
|
31
|
+
```protobuf
|
|
32
|
+
// hero.proto
|
|
33
|
+
syntax = "proto3";
|
|
34
|
+
|
|
35
|
+
package hero;
|
|
36
|
+
|
|
37
|
+
service HeroesService {
|
|
38
|
+
rpc FindOne (HeroById) returns (Hero) {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message HeroById {
|
|
42
|
+
int32 id = 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
message Hero {
|
|
46
|
+
int32 id = 1;
|
|
47
|
+
string name = 2;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## nest-cli.json (Copy Proto Files)
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"compilerOptions": {
|
|
56
|
+
"assets": ["**/*.proto"],
|
|
57
|
+
"watchAssets": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Controller
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
@Controller()
|
|
66
|
+
export class HeroesController {
|
|
67
|
+
@GrpcMethod('HeroesService', 'FindOne')
|
|
68
|
+
findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
|
|
69
|
+
return this.items.find(({ id }) => id === data.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Omit decorator args for auto-binding: `@GrpcMethod()` uses class/method name.
|
|
75
|
+
|
|
76
|
+
## Client
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
ClientsModule.register([
|
|
80
|
+
{
|
|
81
|
+
name: 'HERO_PACKAGE',
|
|
82
|
+
transport: Transport.GRPC,
|
|
83
|
+
options: {
|
|
84
|
+
package: 'hero',
|
|
85
|
+
protoPath: join(__dirname, 'hero/hero.proto'),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
]),
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
@Injectable()
|
|
93
|
+
export class AppService implements OnModuleInit {
|
|
94
|
+
private heroesService: HeroesService;
|
|
95
|
+
|
|
96
|
+
constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {}
|
|
97
|
+
|
|
98
|
+
onModuleInit() {
|
|
99
|
+
this.heroesService = this.client.getService<HeroesService>('HeroesService');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getHero() {
|
|
103
|
+
return this.heroesService.findOne({ id: 1 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## gRPC Metadata
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Send
|
|
112
|
+
const metadata = new Metadata();
|
|
113
|
+
metadata.add('Set-Cookie', 'yummy_cookie=choco');
|
|
114
|
+
return this.heroesService.findOne({ id: 1 }, metadata);
|
|
115
|
+
|
|
116
|
+
// Receive and respond
|
|
117
|
+
findOne(data, metadata: Metadata, call: ServerUnaryCall<any, any>) {
|
|
118
|
+
const serverMetadata = new Metadata();
|
|
119
|
+
serverMetadata.add('Set-Cookie', 'choco');
|
|
120
|
+
call.sendMetadata(serverMetadata);
|
|
121
|
+
return hero;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Streaming
|
|
126
|
+
|
|
127
|
+
Use `@GrpcStreamMethod()` for duplex streams:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
@GrpcStreamMethod()
|
|
131
|
+
bidiHello(messages: Observable<any>, metadata, call): Observable<any> {
|
|
132
|
+
return messages.pipe(
|
|
133
|
+
map((msg) => ({ reply: `Hello, ${msg.greeting}!` })),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or `@GrpcStreamCall()` for raw stream access:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
@GrpcStreamCall()
|
|
142
|
+
bidiHello(requestStream: any) {
|
|
143
|
+
requestStream.on('data', (msg) => {
|
|
144
|
+
requestStream.write({ reply: 'Hello!' });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## gRPC Reflection
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm i @grpc/reflection
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { ReflectionService } from '@grpc/reflection';
|
|
157
|
+
|
|
158
|
+
options: {
|
|
159
|
+
onLoadPackageDefinition: (pkg, server) => {
|
|
160
|
+
new ReflectionService(pkg).addToServer(server);
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Key Points
|
|
166
|
+
|
|
167
|
+
- Use `ClientGrpc.getService()` not `ClientProxy`
|
|
168
|
+
- Methods are lowerCamelCase in TypeScript
|
|
169
|
+
- Set `loader.keepCase: true` for underscore field names
|
|
170
|
+
- Streaming handlers must return `Observable` for full-duplex
|
|
171
|
+
|
|
172
|
+
<!--
|
|
173
|
+
Source references:
|
|
174
|
+
- https://docs.nestjs.com/microservices/grpc
|
|
175
|
+
-->
|