@tinkerstack/graphql-annotation 0.0.4 → 0.0.7
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 +138 -138
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/nest-cli.json +8 -8
- package/package.json +7 -2
- package/src/example/consumer/consumer.app.ts +38 -38
- package/src/example/consumer/consumer.example.ts +57 -57
- package/src/example/main.ts +23 -23
- package/src/example/producer/producer.app.ts +22 -22
- package/src/example/producer/producer.example.ts +28 -28
- package/src/modules/core/annotation.constants.ts +37 -37
- package/src/modules/core/annotation.decorators.ts +161 -161
- package/src/modules/core/annotation.loader.ts +318 -315
- package/src/modules/core/annotation.module.ts +47 -47
- package/src/modules/core/annotation.resolver.ts +87 -87
- package/src/modules/core/annotation.utils.ts +48 -48
- package/src/modules/core/index.ts +4 -4
- package/src/modules/core/resolver.explorer.ts +31 -31
- package/src/modules/index.ts +1 -1
- package/tsconfig.build.json +5 -5
- package/tsconfig.json +30 -30
- package/tsup.config.ts +10 -10
- package/.turbo/turbo-build.log +0 -20
package/README.md
CHANGED
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
<center>
|
|
2
|
-
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.icon-icons.com%2Ficons2%2F2699%2FPNG%2F512%2Fgraphql_logo_icon_171045.png&f=1&nofb=1&ipt=e40678a4804af7f493102aa8bcc97bacbfdfee9dac86e14c75ab7afb00a237b5" height="150px">
|
|
3
|
-
<h1>GraphQL Annotation</h1>
|
|
4
|
-
<p>A set of tools for extending GraphQL stitching capabilities through annotations</p>
|
|
5
|
-
</center>
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Who is this intended for?
|
|
10
|
-
|
|
11
|
-
This library is designed to solve a specific scenario in mind:
|
|
12
|
-
|
|
13
|
-
1. There's a primary `Gateway` and separately developed `SubGateway`s, both of each uses `graphql`.
|
|
14
|
-
2. Front-end or external non-backend consumers interact with `SubGateway` resolvers stitched through `Gateway`
|
|
15
|
-
3. Resolvers at `SubGateway` requires additional resource/guard/processing from `Gateway`.
|
|
16
|
-
4. `SubGateway` team does not have access to `Gateway` nor can they run a local instance.
|
|
17
|
-
5. `SubGateway` team **does not require** `Gateway` features during development, only during deployment.
|
|
18
|
-
|
|
19
|
-
Available solutions often utilize `Remote-Produce-Calls (RPCs)` libraries to let `SubGateway` invoke `Gateway` methods through HTTP transport, which unfortunately fails point 4 and 5, of which this library answers through `annotations`.
|
|
20
|
-
|
|
21
|
-
**However**, if `SubGateway` needs to make additional calls to `Gateway` for mutation or other purposes, then this library should only be considered as a mean of optimization and you are better off just running an `RPC` library at the small of performance and back-and-forth.
|
|
22
|
-
|
|
23
|
-
**TL;DR:** Only use this library if you HAVE TO achieve complete isolation between `Gateway` and `SubGateway` development cycle.
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
You can find examples in `src/examples` for more details. Note that `graphql-annotation` should be replaced with workspace name in internal projects.
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
// gateway
|
|
31
|
-
|
|
32
|
-
import {
|
|
33
|
-
GraphQLAnnotationModule,
|
|
34
|
-
GraphQLAnnotationSchemaLoader
|
|
35
|
-
} from "graphql-annotation";
|
|
36
|
-
|
|
37
|
-
@Module({
|
|
38
|
-
imports: [
|
|
39
|
-
GraphQLAnnotationModule.forRoot(),
|
|
40
|
-
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
41
|
-
driver: ApolloDriver,
|
|
42
|
-
useFactory: (loader: GraphQLAnnotatedSchemaLoader) => ({
|
|
43
|
-
async transformSchema(schema) {
|
|
44
|
-
return stitchSchemas({
|
|
45
|
-
subschemas: [
|
|
46
|
-
{ schema },
|
|
47
|
-
{
|
|
48
|
-
// Load external annotated schemas
|
|
49
|
-
schema: await loader.load({
|
|
50
|
-
example: "http://localhost:3031/graphql",
|
|
51
|
-
}),
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
57
|
-
inject: [GraphQLAnnotatedSchemaLoader],
|
|
58
|
-
}),
|
|
59
|
-
],
|
|
60
|
-
})
|
|
61
|
-
export class GatewayAppModule {}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// It is recommended that you wrap the `Annotate` decorator in a function and in the gateway to ensure `gateway` team knows that's implemented. `sub-gateway` team could reference this through github submodule.
|
|
65
|
-
|
|
66
|
-
export type RoleGroups = ("user" | "admin" | "super-admin")[];
|
|
67
|
-
export const ProtectedResolver = (roles: RoleGroups) =>
|
|
68
|
-
Annotate("protected", { data: { roles } });
|
|
69
|
-
|
|
70
|
-
export const InjectableArg = (argName: string, resourceType: string) =>
|
|
71
|
-
Annotate("resource", { data: { resourceType }, parameter: argName });
|
|
72
|
-
|
|
73
|
-
@Injectable()
|
|
74
|
-
export class ExampleGuard implements CanActivate {
|
|
75
|
-
canActivate(context: ExecutionContext) {
|
|
76
|
-
// Do something with your execution context
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@Resolver()
|
|
82
|
-
export class SubgatewayResolver {
|
|
83
|
-
// NOTE: You can use normal nest and graphql decorators.
|
|
84
|
-
// Just note that `ResolveAnnotation` must be in a `Resolver`
|
|
85
|
-
|
|
86
|
-
@UseGuards(ExampleGuard)
|
|
87
|
-
@ResolveAnnotation("protected")
|
|
88
|
-
verifyUser(context: ExecutionContext, next: () => void) {}
|
|
89
|
-
|
|
90
|
-
// If empty would use function name as annotation tag
|
|
91
|
-
@ResolveAnnotation()
|
|
92
|
-
resource(
|
|
93
|
-
// WARN: this is NOT validated like in graphql though
|
|
94
|
-
@Args("resourceType") resourceType: string,
|
|
95
|
-
@Context() context: GraphQLExecutionContext,
|
|
96
|
-
) {
|
|
97
|
-
return "this-is-gateway-provided-username";
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ---------------------------------------------
|
|
102
|
-
|
|
103
|
-
// sub-gateway
|
|
104
|
-
@Module({
|
|
105
|
-
imports: [
|
|
106
|
-
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
107
|
-
/* Put your graphql config here*/
|
|
108
|
-
}),
|
|
109
|
-
GraphQLAnnotationModule.forRoot({
|
|
110
|
-
serveAnnotations: true,
|
|
111
|
-
// Name is only for identification at the time of writing, which does not support annotation filtering.
|
|
112
|
-
serveConfig: { name: "<subgateway-a>" },
|
|
113
|
-
}),
|
|
114
|
-
ExampleProducerModule,
|
|
115
|
-
],
|
|
116
|
-
})
|
|
117
|
-
export class SubGatewayAppModule {}
|
|
118
|
-
|
|
119
|
-
@Resolver()
|
|
120
|
-
export class ExampleProducerResolver {
|
|
121
|
-
@Query(() => [String])
|
|
122
|
-
@ProtectedResolver(["admin"])
|
|
123
|
-
testProtectedQuery() {
|
|
124
|
-
return ["This resolver should only be available to if guard check passed"];
|
|
125
|
-
}
|
|
126
|
-
@Query(() => [String])
|
|
127
|
-
testQueryWithParam(
|
|
128
|
-
@InjectableArg("username", "session:username")
|
|
129
|
-
username: string
|
|
130
|
-
) {
|
|
131
|
-
return [`Received params: ${username}`];
|
|
132
|
-
}
|
|
133
|
-
@Query(() => [String])
|
|
134
|
-
testPublicQuery() {
|
|
135
|
-
return ["This resolver should be available to everyone"];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
```
|
|
1
|
+
<center>
|
|
2
|
+
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.icon-icons.com%2Ficons2%2F2699%2FPNG%2F512%2Fgraphql_logo_icon_171045.png&f=1&nofb=1&ipt=e40678a4804af7f493102aa8bcc97bacbfdfee9dac86e14c75ab7afb00a237b5" height="150px">
|
|
3
|
+
<h1>GraphQL Annotation</h1>
|
|
4
|
+
<p>A set of tools for extending GraphQL stitching capabilities through annotations</p>
|
|
5
|
+
</center>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Who is this intended for?
|
|
10
|
+
|
|
11
|
+
This library is designed to solve a specific scenario in mind:
|
|
12
|
+
|
|
13
|
+
1. There's a primary `Gateway` and separately developed `SubGateway`s, both of each uses `graphql`.
|
|
14
|
+
2. Front-end or external non-backend consumers interact with `SubGateway` resolvers stitched through `Gateway`
|
|
15
|
+
3. Resolvers at `SubGateway` requires additional resource/guard/processing from `Gateway`.
|
|
16
|
+
4. `SubGateway` team does not have access to `Gateway` nor can they run a local instance.
|
|
17
|
+
5. `SubGateway` team **does not require** `Gateway` features during development, only during deployment.
|
|
18
|
+
|
|
19
|
+
Available solutions often utilize `Remote-Produce-Calls (RPCs)` libraries to let `SubGateway` invoke `Gateway` methods through HTTP transport, which unfortunately fails point 4 and 5, of which this library answers through `annotations`.
|
|
20
|
+
|
|
21
|
+
**However**, if `SubGateway` needs to make additional calls to `Gateway` for mutation or other purposes, then this library should only be considered as a mean of optimization and you are better off just running an `RPC` library at the small of performance and back-and-forth.
|
|
22
|
+
|
|
23
|
+
**TL;DR:** Only use this library if you HAVE TO achieve complete isolation between `Gateway` and `SubGateway` development cycle.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
You can find examples in `src/examples` for more details. Note that `graphql-annotation` should be replaced with workspace name in internal projects.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// gateway
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
GraphQLAnnotationModule,
|
|
34
|
+
GraphQLAnnotationSchemaLoader
|
|
35
|
+
} from "graphql-annotation";
|
|
36
|
+
|
|
37
|
+
@Module({
|
|
38
|
+
imports: [
|
|
39
|
+
GraphQLAnnotationModule.forRoot(),
|
|
40
|
+
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
41
|
+
driver: ApolloDriver,
|
|
42
|
+
useFactory: (loader: GraphQLAnnotatedSchemaLoader) => ({
|
|
43
|
+
async transformSchema(schema) {
|
|
44
|
+
return stitchSchemas({
|
|
45
|
+
subschemas: [
|
|
46
|
+
{ schema },
|
|
47
|
+
{
|
|
48
|
+
// Load external annotated schemas
|
|
49
|
+
schema: await loader.load({
|
|
50
|
+
example: "http://localhost:3031/graphql",
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
inject: [GraphQLAnnotatedSchemaLoader],
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
export class GatewayAppModule {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// It is recommended that you wrap the `Annotate` decorator in a function and in the gateway to ensure `gateway` team knows that's implemented. `sub-gateway` team could reference this through github submodule.
|
|
65
|
+
|
|
66
|
+
export type RoleGroups = ("user" | "admin" | "super-admin")[];
|
|
67
|
+
export const ProtectedResolver = (roles: RoleGroups) =>
|
|
68
|
+
Annotate("protected", { data: { roles } });
|
|
69
|
+
|
|
70
|
+
export const InjectableArg = (argName: string, resourceType: string) =>
|
|
71
|
+
Annotate("resource", { data: { resourceType }, parameter: argName });
|
|
72
|
+
|
|
73
|
+
@Injectable()
|
|
74
|
+
export class ExampleGuard implements CanActivate {
|
|
75
|
+
canActivate(context: ExecutionContext) {
|
|
76
|
+
// Do something with your execution context
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Resolver()
|
|
82
|
+
export class SubgatewayResolver {
|
|
83
|
+
// NOTE: You can use normal nest and graphql decorators.
|
|
84
|
+
// Just note that `ResolveAnnotation` must be in a `Resolver`
|
|
85
|
+
|
|
86
|
+
@UseGuards(ExampleGuard)
|
|
87
|
+
@ResolveAnnotation("protected")
|
|
88
|
+
verifyUser(context: ExecutionContext, next: () => void) {}
|
|
89
|
+
|
|
90
|
+
// If empty would use function name as annotation tag
|
|
91
|
+
@ResolveAnnotation()
|
|
92
|
+
resource(
|
|
93
|
+
// WARN: this is NOT validated like in graphql though
|
|
94
|
+
@Args("resourceType") resourceType: string,
|
|
95
|
+
@Context() context: GraphQLExecutionContext,
|
|
96
|
+
) {
|
|
97
|
+
return "this-is-gateway-provided-username";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------
|
|
102
|
+
|
|
103
|
+
// sub-gateway
|
|
104
|
+
@Module({
|
|
105
|
+
imports: [
|
|
106
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
107
|
+
/* Put your graphql config here*/
|
|
108
|
+
}),
|
|
109
|
+
GraphQLAnnotationModule.forRoot({
|
|
110
|
+
serveAnnotations: true,
|
|
111
|
+
// Name is only for identification at the time of writing, which does not support annotation filtering.
|
|
112
|
+
serveConfig: { name: "<subgateway-a>" },
|
|
113
|
+
}),
|
|
114
|
+
ExampleProducerModule,
|
|
115
|
+
],
|
|
116
|
+
})
|
|
117
|
+
export class SubGatewayAppModule {}
|
|
118
|
+
|
|
119
|
+
@Resolver()
|
|
120
|
+
export class ExampleProducerResolver {
|
|
121
|
+
@Query(() => [String])
|
|
122
|
+
@ProtectedResolver(["admin"])
|
|
123
|
+
testProtectedQuery() {
|
|
124
|
+
return ["This resolver should only be available to if guard check passed"];
|
|
125
|
+
}
|
|
126
|
+
@Query(() => [String])
|
|
127
|
+
testQueryWithParam(
|
|
128
|
+
@InjectableArg("username", "session:username")
|
|
129
|
+
username: string
|
|
130
|
+
) {
|
|
131
|
+
return [`Received params: ${username}`];
|
|
132
|
+
}
|
|
133
|
+
@Query(() => [String])
|
|
134
|
+
testPublicQuery() {
|
|
135
|
+
return ["This resolver should be available to everyone"];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -373,10 +373,10 @@ var GraphQLAnnotatedSchemaLoader = class {
|
|
|
373
373
|
}
|
|
374
374
|
});
|
|
375
375
|
const [loaded, failed] = split(await Promise.allSettled(pending), (q) => q.status === "fulfilled");
|
|
376
|
-
const shouldThrow = opts?.ignoreMissingSources ??
|
|
376
|
+
const shouldThrow = !(opts?.ignoreMissingSources ?? false);
|
|
377
377
|
if (failed.length > 0) {
|
|
378
378
|
if (shouldThrow) throw new Error(`Failed to load ${failed.length} schema(s) from sources.`);
|
|
379
|
-
else this._logger.warn(`Failed to load ${failed.length}
|
|
379
|
+
else this._logger.warn(`Failed to load ${failed.length} schema(s) from sources.`);
|
|
380
380
|
}
|
|
381
381
|
return loaded.map((l) => l.value);
|
|
382
382
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -344,10 +344,10 @@ var GraphQLAnnotatedSchemaLoader = class {
|
|
|
344
344
|
}
|
|
345
345
|
});
|
|
346
346
|
const [loaded, failed] = split(await Promise.allSettled(pending), (q) => q.status === "fulfilled");
|
|
347
|
-
const shouldThrow = opts?.ignoreMissingSources ??
|
|
347
|
+
const shouldThrow = !(opts?.ignoreMissingSources ?? false);
|
|
348
348
|
if (failed.length > 0) {
|
|
349
349
|
if (shouldThrow) throw new Error(`Failed to load ${failed.length} schema(s) from sources.`);
|
|
350
|
-
else this._logger.warn(`Failed to load ${failed.length}
|
|
350
|
+
else this._logger.warn(`Failed to load ${failed.length} schema(s) from sources.`);
|
|
351
351
|
}
|
|
352
352
|
return loaded.map((l) => l.value);
|
|
353
353
|
}
|
package/nest-cli.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json.schemastore.org/nest-cli",
|
|
3
|
-
"collection": "@nestjs/schematics",
|
|
4
|
-
"sourceRoot": "src",
|
|
5
|
-
"entryFile": "example/main",
|
|
6
|
-
"compilerOptions": {
|
|
7
|
-
"tsConfigPath": "tsconfig.json"
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/nest-cli",
|
|
3
|
+
"collection": "@nestjs/schematics",
|
|
4
|
+
"sourceRoot": "src",
|
|
5
|
+
"entryFile": "example/main",
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"tsConfigPath": "tsconfig.json"
|
|
8
|
+
}
|
|
9
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinkerstack/graphql-annotation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Better type-safe-able graphql directives",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"graphql-request": "^7.2.0"
|
|
18
18
|
},
|
|
19
19
|
"publishConfig": {
|
|
20
|
-
"access": "public"
|
|
20
|
+
"access": "public",
|
|
21
|
+
"provenance": true
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/plasteek/tinkerstack.git"
|
|
21
26
|
},
|
|
22
27
|
"peerDependencies": {
|
|
23
28
|
"@nestjs/common": "^10 || ^11",
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { Module } from "@nestjs/common";
|
|
2
|
-
import { GraphQLModule } from "@nestjs/graphql";
|
|
3
|
-
|
|
4
|
-
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
5
|
-
import { ExampleConsumerModule } from "./consumer.example";
|
|
6
|
-
import { GraphQLAnnotatedSchemaLoader } from "@/modules/core/annotation.loader";
|
|
7
|
-
import { GraphQLAnnotationModule } from "@/modules/core/annotation.module";
|
|
8
|
-
import { stitchSchemas } from "@graphql-tools/stitch";
|
|
9
|
-
|
|
10
|
-
@Module({
|
|
11
|
-
imports: [
|
|
12
|
-
ExampleConsumerModule,
|
|
13
|
-
GraphQLAnnotationModule.forRoot(),
|
|
14
|
-
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
15
|
-
driver: ApolloDriver,
|
|
16
|
-
useFactory: (loader: GraphQLAnnotatedSchemaLoader) => ({
|
|
17
|
-
graphiql: true,
|
|
18
|
-
playground: true,
|
|
19
|
-
autoSchemaFile: "./src/example/consumer/consumer.schema.gql",
|
|
20
|
-
async transformSchema(schema) {
|
|
21
|
-
return stitchSchemas({
|
|
22
|
-
subschemas: [
|
|
23
|
-
{ schema },
|
|
24
|
-
{
|
|
25
|
-
// Load external annotated schemas
|
|
26
|
-
schema: await loader.load({
|
|
27
|
-
example: "http://localhost:3031/graphql",
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
});
|
|
32
|
-
},
|
|
33
|
-
}),
|
|
34
|
-
inject: [GraphQLAnnotatedSchemaLoader],
|
|
35
|
-
}),
|
|
36
|
-
],
|
|
37
|
-
})
|
|
38
|
-
export class ExampleConsumerApp {}
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { GraphQLModule } from "@nestjs/graphql";
|
|
3
|
+
|
|
4
|
+
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
5
|
+
import { ExampleConsumerModule } from "./consumer.example";
|
|
6
|
+
import { GraphQLAnnotatedSchemaLoader } from "@/modules/core/annotation.loader";
|
|
7
|
+
import { GraphQLAnnotationModule } from "@/modules/core/annotation.module";
|
|
8
|
+
import { stitchSchemas } from "@graphql-tools/stitch";
|
|
9
|
+
|
|
10
|
+
@Module({
|
|
11
|
+
imports: [
|
|
12
|
+
ExampleConsumerModule,
|
|
13
|
+
GraphQLAnnotationModule.forRoot(),
|
|
14
|
+
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
15
|
+
driver: ApolloDriver,
|
|
16
|
+
useFactory: (loader: GraphQLAnnotatedSchemaLoader) => ({
|
|
17
|
+
graphiql: true,
|
|
18
|
+
playground: true,
|
|
19
|
+
autoSchemaFile: "./src/example/consumer/consumer.schema.gql",
|
|
20
|
+
async transformSchema(schema) {
|
|
21
|
+
return stitchSchemas({
|
|
22
|
+
subschemas: [
|
|
23
|
+
{ schema },
|
|
24
|
+
{
|
|
25
|
+
// Load external annotated schemas
|
|
26
|
+
schema: await loader.load({
|
|
27
|
+
example: "http://localhost:3031/graphql",
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
inject: [GraphQLAnnotatedSchemaLoader],
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
export class ExampleConsumerApp {}
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { Annotate, ResolveAnnotation } from "@/modules/core";
|
|
2
|
-
import {
|
|
3
|
-
CanActivate,
|
|
4
|
-
ExecutionContext,
|
|
5
|
-
Injectable,
|
|
6
|
-
Module,
|
|
7
|
-
UseGuards,
|
|
8
|
-
} from "@nestjs/common";
|
|
9
|
-
import {
|
|
10
|
-
Args,
|
|
11
|
-
Context,
|
|
12
|
-
GraphQLExecutionContext,
|
|
13
|
-
Query,
|
|
14
|
-
Resolver,
|
|
15
|
-
} from "@nestjs/graphql";
|
|
16
|
-
|
|
17
|
-
// Export for external
|
|
18
|
-
export type RoleGroups = ("user" | "admin" | "super-admin")[];
|
|
19
|
-
export const ProtectedResolver = (roles: RoleGroups) =>
|
|
20
|
-
Annotate("protected", { data: { roles } });
|
|
21
|
-
export const InjectableArg = (argName: string, resourceType: string) =>
|
|
22
|
-
Annotate("resource", { data: { resourceType }, parameter: argName });
|
|
23
|
-
|
|
24
|
-
@Injectable()
|
|
25
|
-
export class ExampleGuard implements CanActivate {
|
|
26
|
-
canActivate(context: ExecutionContext) {
|
|
27
|
-
// Do something with your execution context
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@Resolver()
|
|
33
|
-
export class ExampleConsumerResolver {
|
|
34
|
-
@Query(() => [String])
|
|
35
|
-
exampleOut() {
|
|
36
|
-
return ["this is from consumer"];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@UseGuards(ExampleGuard)
|
|
40
|
-
@ResolveAnnotation("protected")
|
|
41
|
-
verifyUser(context: ExecutionContext, next: () => void) {
|
|
42
|
-
console.log("I AM HERE");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Should use the name
|
|
46
|
-
@ResolveAnnotation("resource")
|
|
47
|
-
resolveResource(
|
|
48
|
-
@Args("resourceType") resourceType: string,
|
|
49
|
-
@Context() context: GraphQLExecutionContext,
|
|
50
|
-
) {
|
|
51
|
-
console.log(resourceType);
|
|
52
|
-
return "this-is-gateway-provided-username";
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@Module({ providers: [ExampleConsumerResolver] })
|
|
57
|
-
export class ExampleConsumerModule {}
|
|
1
|
+
import { Annotate, ResolveAnnotation } from "@/modules/core";
|
|
2
|
+
import {
|
|
3
|
+
CanActivate,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
Injectable,
|
|
6
|
+
Module,
|
|
7
|
+
UseGuards,
|
|
8
|
+
} from "@nestjs/common";
|
|
9
|
+
import {
|
|
10
|
+
Args,
|
|
11
|
+
Context,
|
|
12
|
+
GraphQLExecutionContext,
|
|
13
|
+
Query,
|
|
14
|
+
Resolver,
|
|
15
|
+
} from "@nestjs/graphql";
|
|
16
|
+
|
|
17
|
+
// Export for external
|
|
18
|
+
export type RoleGroups = ("user" | "admin" | "super-admin")[];
|
|
19
|
+
export const ProtectedResolver = (roles: RoleGroups) =>
|
|
20
|
+
Annotate("protected", { data: { roles } });
|
|
21
|
+
export const InjectableArg = (argName: string, resourceType: string) =>
|
|
22
|
+
Annotate("resource", { data: { resourceType }, parameter: argName });
|
|
23
|
+
|
|
24
|
+
@Injectable()
|
|
25
|
+
export class ExampleGuard implements CanActivate {
|
|
26
|
+
canActivate(context: ExecutionContext) {
|
|
27
|
+
// Do something with your execution context
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Resolver()
|
|
33
|
+
export class ExampleConsumerResolver {
|
|
34
|
+
@Query(() => [String])
|
|
35
|
+
exampleOut() {
|
|
36
|
+
return ["this is from consumer"];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@UseGuards(ExampleGuard)
|
|
40
|
+
@ResolveAnnotation("protected")
|
|
41
|
+
verifyUser(context: ExecutionContext, next: () => void) {
|
|
42
|
+
console.log("I AM HERE");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Should use the name
|
|
46
|
+
@ResolveAnnotation("resource")
|
|
47
|
+
resolveResource(
|
|
48
|
+
@Args("resourceType") resourceType: string,
|
|
49
|
+
@Context() context: GraphQLExecutionContext,
|
|
50
|
+
) {
|
|
51
|
+
console.log(resourceType);
|
|
52
|
+
return "this-is-gateway-provided-username";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Module({ providers: [ExampleConsumerResolver] })
|
|
57
|
+
export class ExampleConsumerModule {}
|
package/src/example/main.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { NestFactory } from "@nestjs/core";
|
|
2
|
-
|
|
3
|
-
import { FastifyAdapter } from "@nestjs/platform-fastify";
|
|
4
|
-
import { ExampleProducerApp } from "./producer/producer.app";
|
|
5
|
-
import { ConsoleLogger, Type } from "@nestjs/common";
|
|
6
|
-
import { ExampleConsumerApp } from "./consumer/consumer.app";
|
|
7
|
-
|
|
8
|
-
export async function bootstrap(name: string, module: Type, port: number) {
|
|
9
|
-
const app = await NestFactory.create(module, new FastifyAdapter(), {
|
|
10
|
-
logger: new ConsoleLogger({ prefix: name }),
|
|
11
|
-
});
|
|
12
|
-
await app.listen(port);
|
|
13
|
-
|
|
14
|
-
console.log(
|
|
15
|
-
`Listening on localhost:${port}. Playground: http://localhost:${port}/graphql`
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function main() {
|
|
20
|
-
await bootstrap("example-producer", ExampleProducerApp, 3031);
|
|
21
|
-
await bootstrap("example-consumer", ExampleConsumerApp, 3030);
|
|
22
|
-
}
|
|
23
|
-
void main();
|
|
1
|
+
import { NestFactory } from "@nestjs/core";
|
|
2
|
+
|
|
3
|
+
import { FastifyAdapter } from "@nestjs/platform-fastify";
|
|
4
|
+
import { ExampleProducerApp } from "./producer/producer.app";
|
|
5
|
+
import { ConsoleLogger, Type } from "@nestjs/common";
|
|
6
|
+
import { ExampleConsumerApp } from "./consumer/consumer.app";
|
|
7
|
+
|
|
8
|
+
export async function bootstrap(name: string, module: Type, port: number) {
|
|
9
|
+
const app = await NestFactory.create(module, new FastifyAdapter(), {
|
|
10
|
+
logger: new ConsoleLogger({ prefix: name }),
|
|
11
|
+
});
|
|
12
|
+
await app.listen(port);
|
|
13
|
+
|
|
14
|
+
console.log(
|
|
15
|
+
`Listening on localhost:${port}. Playground: http://localhost:${port}/graphql`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
await bootstrap("example-producer", ExampleProducerApp, 3031);
|
|
21
|
+
await bootstrap("example-consumer", ExampleConsumerApp, 3030);
|
|
22
|
+
}
|
|
23
|
+
void main();
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { Module } from "@nestjs/common";
|
|
2
|
-
import { GraphQLModule } from "@nestjs/graphql";
|
|
3
|
-
import { ExampleProducerModule } from "./producer.example";
|
|
4
|
-
|
|
5
|
-
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
6
|
-
import { GraphQLAnnotationModule } from "@/modules/core/annotation.module";
|
|
7
|
-
|
|
8
|
-
@Module({
|
|
9
|
-
imports: [
|
|
10
|
-
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
11
|
-
driver: ApolloDriver,
|
|
12
|
-
graphiql: true,
|
|
13
|
-
autoSchemaFile: "./src/example/producer/producer.schema.gql",
|
|
14
|
-
}),
|
|
15
|
-
GraphQLAnnotationModule.forRoot({
|
|
16
|
-
serveAnnotations: true,
|
|
17
|
-
serveConfig: { name: "producer-example" },
|
|
18
|
-
}),
|
|
19
|
-
ExampleProducerModule,
|
|
20
|
-
],
|
|
21
|
-
})
|
|
22
|
-
export class ExampleProducerApp {}
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { GraphQLModule } from "@nestjs/graphql";
|
|
3
|
+
import { ExampleProducerModule } from "./producer.example";
|
|
4
|
+
|
|
5
|
+
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
6
|
+
import { GraphQLAnnotationModule } from "@/modules/core/annotation.module";
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
11
|
+
driver: ApolloDriver,
|
|
12
|
+
graphiql: true,
|
|
13
|
+
autoSchemaFile: "./src/example/producer/producer.schema.gql",
|
|
14
|
+
}),
|
|
15
|
+
GraphQLAnnotationModule.forRoot({
|
|
16
|
+
serveAnnotations: true,
|
|
17
|
+
serveConfig: { name: "producer-example" },
|
|
18
|
+
}),
|
|
19
|
+
ExampleProducerModule,
|
|
20
|
+
],
|
|
21
|
+
})
|
|
22
|
+
export class ExampleProducerApp {}
|