@tinkerstack/graphql-annotation 0.0.0
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/.turbo/turbo-build.log +22 -0
- package/README.md +138 -0
- package/dist/index.d.mts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +511 -0
- package/dist/index.mjs +484 -0
- package/nest-cli.json +9 -0
- package/package.json +54 -0
- package/src/example/consumer/consumer.app.ts +38 -0
- package/src/example/consumer/consumer.example.ts +57 -0
- package/src/example/consumer/consumer.schema.gql +7 -0
- package/src/example/main.ts +23 -0
- package/src/example/producer/producer.app.ts +22 -0
- package/src/example/producer/producer.example.ts +28 -0
- package/src/example/producer/producer.schema.gql +20 -0
- package/src/index.ts +1 -0
- package/src/modules/core/annotation.constants.ts +37 -0
- package/src/modules/core/annotation.decorators.ts +147 -0
- package/src/modules/core/annotation.loader.ts +303 -0
- package/src/modules/core/annotation.module.ts +47 -0
- package/src/modules/core/annotation.resolver.ts +86 -0
- package/src/modules/core/annotation.utils.ts +48 -0
- package/src/modules/core/index.ts +4 -0
- package/src/modules/core/resolver.explorer.ts +31 -0
- package/src/modules/index.ts +1 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +10 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
5
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
6
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
7
|
+
if (decorator = decorators[i])
|
|
8
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
9
|
+
if (kind && result) __defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
13
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14
|
+
|
|
15
|
+
// src/modules/core/annotation.decorators.ts
|
|
16
|
+
import { SetMetadata } from "@nestjs/common";
|
|
17
|
+
import { Args } from "@nestjs/graphql";
|
|
18
|
+
import "reflect-metadata";
|
|
19
|
+
|
|
20
|
+
// src/modules/core/annotation.constants.ts
|
|
21
|
+
var ANNOTATION_METADATA = "graphql-annotation";
|
|
22
|
+
var ANNOTATION_RESOLVER_METADATA = "graphql-annotation-resolver";
|
|
23
|
+
var GRAPHQL_ANNOTATION_RESOLVER_METADATA = "graphql-annotation-provider";
|
|
24
|
+
|
|
25
|
+
// src/modules/core/annotation.decorators.ts
|
|
26
|
+
function Annotate(name, opts) {
|
|
27
|
+
const annotationName = name;
|
|
28
|
+
const annotationData = opts?.data;
|
|
29
|
+
return (target, propertyKey, descriptorOrParameterIndex) => {
|
|
30
|
+
switch (typeof descriptorOrParameterIndex) {
|
|
31
|
+
// Method
|
|
32
|
+
case "object": {
|
|
33
|
+
const descriptor = descriptorOrParameterIndex;
|
|
34
|
+
_updateAnnotation(descriptor.value ?? target, (meta) => ({
|
|
35
|
+
...meta,
|
|
36
|
+
[annotationName]: {
|
|
37
|
+
annotation: annotationName,
|
|
38
|
+
data: annotationData,
|
|
39
|
+
target: {
|
|
40
|
+
type: "method",
|
|
41
|
+
name: propertyKey
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
// Parameter
|
|
48
|
+
case "number": {
|
|
49
|
+
const paramIndex = descriptorOrParameterIndex;
|
|
50
|
+
const paramName = opts?.parameter;
|
|
51
|
+
if (!paramName)
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Unable to apply annotation "${annotationName}". Parameter name not specified.`
|
|
54
|
+
);
|
|
55
|
+
Args(paramName)(target, propertyKey, paramIndex);
|
|
56
|
+
_updateAnnotation(
|
|
57
|
+
target.constructor,
|
|
58
|
+
(meta) => ({
|
|
59
|
+
...meta,
|
|
60
|
+
[`${annotationName}@${paramIndex}`]: {
|
|
61
|
+
annotation: annotationName,
|
|
62
|
+
data: annotationData,
|
|
63
|
+
target: {
|
|
64
|
+
type: "parameter",
|
|
65
|
+
paramIndex,
|
|
66
|
+
paramName
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
propertyKey
|
|
71
|
+
);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Unable to attach annotation "${name}". Unsupported target.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function _updateAnnotation(target, updateFn, propertyKey) {
|
|
82
|
+
const meta = Reflect.getMetadata(ANNOTATION_METADATA, target) ?? {};
|
|
83
|
+
if (propertyKey)
|
|
84
|
+
Reflect.defineMetadata(
|
|
85
|
+
ANNOTATION_METADATA,
|
|
86
|
+
updateFn(meta),
|
|
87
|
+
target,
|
|
88
|
+
propertyKey
|
|
89
|
+
);
|
|
90
|
+
else Reflect.defineMetadata(ANNOTATION_METADATA, updateFn(meta), target);
|
|
91
|
+
}
|
|
92
|
+
function ResolveAnnotation(name) {
|
|
93
|
+
return (target, property, descriptor) => {
|
|
94
|
+
SetMetadata(ANNOTATION_RESOLVER_METADATA, {
|
|
95
|
+
annotation: name ?? property
|
|
96
|
+
})(target, property, descriptor);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/modules/core/annotation.loader.ts
|
|
101
|
+
import { stitchSchemas } from "@graphql-tools/stitch";
|
|
102
|
+
import { MapperKind, mapSchema } from "@graphql-tools/utils";
|
|
103
|
+
import { FilterRootFields } from "@graphql-tools/wrap";
|
|
104
|
+
import { Injectable as Injectable2, Logger as Logger2 } from "@nestjs/common";
|
|
105
|
+
import {
|
|
106
|
+
PARAM_ARGS_METADATA
|
|
107
|
+
} from "@nestjs/graphql";
|
|
108
|
+
import { GqlParamtype } from "@nestjs/graphql/dist/enums/gql-paramtype.enum";
|
|
109
|
+
import { GraphQLClient } from "graphql-request";
|
|
110
|
+
|
|
111
|
+
// src/modules/core/annotation.resolver.ts
|
|
112
|
+
import { Inject, Injectable, Logger } from "@nestjs/common";
|
|
113
|
+
import { Field, ObjectType, Query, Resolver } from "@nestjs/graphql";
|
|
114
|
+
import { GraphQLJSONObject } from "graphql-scalars";
|
|
115
|
+
import "reflect-metadata";
|
|
116
|
+
var GraphQLAnnotationMeta = class {
|
|
117
|
+
name;
|
|
118
|
+
resolvers;
|
|
119
|
+
};
|
|
120
|
+
__decorateClass([
|
|
121
|
+
Field(() => String)
|
|
122
|
+
], GraphQLAnnotationMeta.prototype, "name", 2);
|
|
123
|
+
__decorateClass([
|
|
124
|
+
Field(() => GraphQLJSONObject, { name: "resolvers" })
|
|
125
|
+
], GraphQLAnnotationMeta.prototype, "resolvers", 2);
|
|
126
|
+
GraphQLAnnotationMeta = __decorateClass([
|
|
127
|
+
ObjectType()
|
|
128
|
+
], GraphQLAnnotationMeta);
|
|
129
|
+
var GraphQLAnnotationResolver = class {
|
|
130
|
+
constructor(metadataScanner, resolverDiscoveryService, meta) {
|
|
131
|
+
this.metadataScanner = metadataScanner;
|
|
132
|
+
this.resolverDiscoveryService = resolverDiscoveryService;
|
|
133
|
+
this.meta = meta;
|
|
134
|
+
}
|
|
135
|
+
_logger = new Logger("GraphQLAnnotationModule");
|
|
136
|
+
async getGraphQLAnnotations() {
|
|
137
|
+
return {
|
|
138
|
+
...this.meta,
|
|
139
|
+
resolvers: this.resolverAnnotations
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
resolverAnnotations = {};
|
|
143
|
+
async onModuleInit() {
|
|
144
|
+
this.resolverAnnotations = this.compileResolversAnnotations();
|
|
145
|
+
}
|
|
146
|
+
compileResolversAnnotations() {
|
|
147
|
+
const instances = this.resolverDiscoveryService.explore();
|
|
148
|
+
const resolversAnnotations = {};
|
|
149
|
+
for (const { instance } of instances)
|
|
150
|
+
for (const methodName of this.metadataScanner.getAllMethodNames(
|
|
151
|
+
Object.getPrototypeOf(instance)
|
|
152
|
+
)) {
|
|
153
|
+
const annotations = [
|
|
154
|
+
...Object.values(
|
|
155
|
+
Reflect.getMetadata(ANNOTATION_METADATA, instance[methodName]) ?? {}
|
|
156
|
+
),
|
|
157
|
+
...Object.values(
|
|
158
|
+
Reflect.getMetadata(
|
|
159
|
+
ANNOTATION_METADATA,
|
|
160
|
+
instance.constructor,
|
|
161
|
+
methodName
|
|
162
|
+
) ?? {}
|
|
163
|
+
)
|
|
164
|
+
];
|
|
165
|
+
if (annotations.length <= 0) continue;
|
|
166
|
+
resolversAnnotations[methodName] = annotations;
|
|
167
|
+
this._logger.log(
|
|
168
|
+
`Discovered annotated resolver "${instance.constructor.name}.${methodName}" with ${annotations.length} annotation(s)`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return resolversAnnotations;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
__publicField(GraphQLAnnotationResolver, "QUERY_NAME", "_annotations");
|
|
175
|
+
__decorateClass([
|
|
176
|
+
Query(() => GraphQLAnnotationMeta, {
|
|
177
|
+
name: GraphQLAnnotationResolver.QUERY_NAME
|
|
178
|
+
})
|
|
179
|
+
], GraphQLAnnotationResolver.prototype, "getGraphQLAnnotations", 1);
|
|
180
|
+
GraphQLAnnotationResolver = __decorateClass([
|
|
181
|
+
Resolver(),
|
|
182
|
+
Injectable(),
|
|
183
|
+
__decorateParam(2, Inject(ANNOTATION_RESOLVER_METADATA))
|
|
184
|
+
], GraphQLAnnotationResolver);
|
|
185
|
+
|
|
186
|
+
// src/modules/core/annotation.utils.ts
|
|
187
|
+
import { buildHTTPExecutor } from "@graphql-tools/executor-http";
|
|
188
|
+
import { schemaFromExecutor } from "@graphql-tools/wrap";
|
|
189
|
+
async function loadGraphQLSchema(url) {
|
|
190
|
+
const executor = buildHTTPExecutor({
|
|
191
|
+
endpoint: url
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
schema: await schemaFromExecutor(executor),
|
|
195
|
+
executor
|
|
196
|
+
// Has to be included.
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function split(array, predicate) {
|
|
200
|
+
const match = [];
|
|
201
|
+
const rest = [];
|
|
202
|
+
array.forEach((item) => {
|
|
203
|
+
const matchedPredicate = predicate(item);
|
|
204
|
+
const target = matchedPredicate ? match : rest;
|
|
205
|
+
target.push(item);
|
|
206
|
+
});
|
|
207
|
+
return [
|
|
208
|
+
match,
|
|
209
|
+
rest
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
function shallowMerge(objects) {
|
|
213
|
+
const res = {};
|
|
214
|
+
objects.forEach((o) => Object.assign(res, o));
|
|
215
|
+
return res;
|
|
216
|
+
}
|
|
217
|
+
function isPlainObject(obj) {
|
|
218
|
+
const prototype = Object.getPrototypeOf(obj);
|
|
219
|
+
return prototype === Object.getPrototypeOf({}) || prototype === null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/modules/core/annotation.loader.ts
|
|
223
|
+
import { instanceToPlain } from "class-transformer";
|
|
224
|
+
var GraphQLAnnotatedSchemaLoader = class {
|
|
225
|
+
constructor(resolverDiscoveryService, metadataScanner, externalContextCreator) {
|
|
226
|
+
this.resolverDiscoveryService = resolverDiscoveryService;
|
|
227
|
+
this.metadataScanner = metadataScanner;
|
|
228
|
+
this.externalContextCreator = externalContextCreator;
|
|
229
|
+
}
|
|
230
|
+
_logger = new Logger2("GraphQLAnnotationModule");
|
|
231
|
+
async load(sources) {
|
|
232
|
+
const loadedSources = await this.batchLoadSources(sources);
|
|
233
|
+
const schema = stitchSchemas({
|
|
234
|
+
subschemas: loadedSources.map((s) => s.subschema)
|
|
235
|
+
});
|
|
236
|
+
const annotations = shallowMerge(
|
|
237
|
+
loadedSources.map((s) => s.annotation)
|
|
238
|
+
);
|
|
239
|
+
const annotationResolvers = await this.discoverAnnotationResolvers();
|
|
240
|
+
return this.embedAnnotationResolversToSchema(
|
|
241
|
+
schema,
|
|
242
|
+
annotations,
|
|
243
|
+
annotationResolvers
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
async discoverAnnotationResolvers() {
|
|
247
|
+
const annotationResolvers = /* @__PURE__ */ new Map();
|
|
248
|
+
const paramFactory = new GraphQLAnnotationResolverParamsFactory();
|
|
249
|
+
const instances = this.resolverDiscoveryService.explore();
|
|
250
|
+
for (const { instance } of instances)
|
|
251
|
+
for (const methodName of this.metadataScanner.getAllMethodNames(
|
|
252
|
+
Object.getPrototypeOf(instance)
|
|
253
|
+
)) {
|
|
254
|
+
const resolverMeta = Reflect.getMetadata(
|
|
255
|
+
ANNOTATION_RESOLVER_METADATA,
|
|
256
|
+
instance[methodName]
|
|
257
|
+
);
|
|
258
|
+
if (!resolverMeta) continue;
|
|
259
|
+
this._logger.log(
|
|
260
|
+
`Discovered resolver for annotation "${resolverMeta.annotation}"`
|
|
261
|
+
);
|
|
262
|
+
annotationResolvers.set(
|
|
263
|
+
resolverMeta.annotation,
|
|
264
|
+
this.externalContextCreator.create(
|
|
265
|
+
instance,
|
|
266
|
+
instance[methodName],
|
|
267
|
+
methodName,
|
|
268
|
+
PARAM_ARGS_METADATA,
|
|
269
|
+
paramFactory,
|
|
270
|
+
void 0,
|
|
271
|
+
void 0,
|
|
272
|
+
void 0,
|
|
273
|
+
"graphql"
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return annotationResolvers;
|
|
278
|
+
}
|
|
279
|
+
async batchLoadSources(sources) {
|
|
280
|
+
const pending = Object.entries(sources).map(async ([name, url]) => {
|
|
281
|
+
const loadId = `${name}@${url}`;
|
|
282
|
+
try {
|
|
283
|
+
const loaded2 = {
|
|
284
|
+
id: loadId,
|
|
285
|
+
url,
|
|
286
|
+
name,
|
|
287
|
+
subschema: await loadGraphQLSchema(url),
|
|
288
|
+
annotation: await this.loadAnnotations(url)
|
|
289
|
+
};
|
|
290
|
+
loaded2.subschema.transforms = [
|
|
291
|
+
new FilterRootFields(
|
|
292
|
+
(op, fieldName) => !fieldName.endsWith(GraphQLAnnotationResolver.QUERY_NAME)
|
|
293
|
+
)
|
|
294
|
+
];
|
|
295
|
+
this._logger.log(`Loaded schema for ${loadId}`);
|
|
296
|
+
return loaded2;
|
|
297
|
+
} catch (err) {
|
|
298
|
+
this._logger.error(`Unable to load schema for ${loadId}`);
|
|
299
|
+
throw err;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
const [loaded, failed] = split(
|
|
303
|
+
await Promise.allSettled(pending),
|
|
304
|
+
(q) => q.status === "fulfilled"
|
|
305
|
+
);
|
|
306
|
+
if (failed.length > 0)
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Failed to load ${failed.length} schema(s) from sources.`
|
|
309
|
+
);
|
|
310
|
+
return loaded.map((l) => l.value);
|
|
311
|
+
}
|
|
312
|
+
async loadAnnotations(url) {
|
|
313
|
+
const client = new GraphQLClient(url);
|
|
314
|
+
try {
|
|
315
|
+
const res = await client.rawRequest(`
|
|
316
|
+
query IntrospectAnnotations {
|
|
317
|
+
${GraphQLAnnotationResolver.QUERY_NAME} {
|
|
318
|
+
name
|
|
319
|
+
resolvers
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
`);
|
|
323
|
+
this._logger.log(`Loaded annotations for ${url}`);
|
|
324
|
+
return res.data[GraphQLAnnotationResolver.QUERY_NAME];
|
|
325
|
+
} catch (err) {
|
|
326
|
+
this._logger.warn(`Failed to load annotations at ${url}, assuming empty`);
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async embedAnnotationResolversToSchema(schema, annotations, annotationResolvers) {
|
|
331
|
+
return mapSchema(schema, {
|
|
332
|
+
[MapperKind.ROOT_FIELD]: (fieldSchema, name, type) => {
|
|
333
|
+
const fieldAnnotations = (annotations.resolvers[name] ?? []).map((info) => ({
|
|
334
|
+
...info,
|
|
335
|
+
resolver: annotationResolvers.get(info.annotation)
|
|
336
|
+
// NOTE: should be validated in filter
|
|
337
|
+
})).filter((info) => {
|
|
338
|
+
const annotationHasResolver = info.resolver !== void 0;
|
|
339
|
+
if (!annotationHasResolver)
|
|
340
|
+
this._logger.warn(
|
|
341
|
+
`Found unhandled annotation "${info.annotation}", skipping linking process`
|
|
342
|
+
);
|
|
343
|
+
return annotationHasResolver;
|
|
344
|
+
});
|
|
345
|
+
if (fieldAnnotations.length <= 0) return fieldSchema;
|
|
346
|
+
for (const annotation of fieldAnnotations)
|
|
347
|
+
if (annotation.target.type === "parameter" && fieldSchema.args)
|
|
348
|
+
delete fieldSchema.args[annotation.target.paramName];
|
|
349
|
+
const defaultResolver = fieldSchema.resolve;
|
|
350
|
+
fieldSchema.resolve = async function(parent, args, context, info) {
|
|
351
|
+
const annotationCallbacks = await Promise.all(
|
|
352
|
+
fieldAnnotations.map(async (annotation) => ({
|
|
353
|
+
annotation,
|
|
354
|
+
return: await annotation.resolver(
|
|
355
|
+
...[
|
|
356
|
+
info.rootValue,
|
|
357
|
+
annotation.data,
|
|
358
|
+
context,
|
|
359
|
+
info
|
|
360
|
+
]
|
|
361
|
+
)
|
|
362
|
+
}))
|
|
363
|
+
);
|
|
364
|
+
const resolvedParams = annotationCallbacks.reduce(
|
|
365
|
+
(params, call) => {
|
|
366
|
+
if (call.annotation.target.type !== "parameter") return params;
|
|
367
|
+
const returnedClassType = typeof call.return === "object" && !isPlainObject(call.return);
|
|
368
|
+
const returnValue = returnedClassType ? instanceToPlain(call.return) : call.return;
|
|
369
|
+
params[call.annotation.target.paramName] = returnValue;
|
|
370
|
+
return params;
|
|
371
|
+
},
|
|
372
|
+
{}
|
|
373
|
+
);
|
|
374
|
+
return defaultResolver(
|
|
375
|
+
parent,
|
|
376
|
+
{ ...args, ...resolvedParams },
|
|
377
|
+
context,
|
|
378
|
+
info
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
return fieldSchema;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
GraphQLAnnotatedSchemaLoader = __decorateClass([
|
|
387
|
+
Injectable2()
|
|
388
|
+
], GraphQLAnnotatedSchemaLoader);
|
|
389
|
+
var GraphQLAnnotationResolverParamsFactory = class {
|
|
390
|
+
// This param factory extends graphql's own just with different inputs from annotations instead.
|
|
391
|
+
// https://github.com/nestjs/graphql/blob/master/packages/graphql/lib/factories/params.factory.ts#L9
|
|
392
|
+
exchangeKeyForValue(type, possibleKey, argsContext) {
|
|
393
|
+
if (!argsContext) return null;
|
|
394
|
+
console.log(type, possibleKey, argsContext);
|
|
395
|
+
const args = {
|
|
396
|
+
parentValue: argsContext[0],
|
|
397
|
+
data: argsContext[1],
|
|
398
|
+
context: argsContext[2],
|
|
399
|
+
info: argsContext[3]
|
|
400
|
+
};
|
|
401
|
+
switch (type) {
|
|
402
|
+
case GqlParamtype.ROOT:
|
|
403
|
+
return args.parentValue;
|
|
404
|
+
case GqlParamtype.ARGS:
|
|
405
|
+
return possibleKey && args.data ? args.data[possibleKey] : args.data;
|
|
406
|
+
case GqlParamtype.CONTEXT:
|
|
407
|
+
return possibleKey && args.context ? args.context[possibleKey] : args.context;
|
|
408
|
+
case GqlParamtype.INFO:
|
|
409
|
+
return possibleKey && args.context ? args.info[possibleKey] : args.info;
|
|
410
|
+
default:
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/modules/core/annotation.module.ts
|
|
417
|
+
import { Module } from "@nestjs/common";
|
|
418
|
+
import { DiscoveryModule } from "@nestjs/core";
|
|
419
|
+
|
|
420
|
+
// src/modules/core/resolver.explorer.ts
|
|
421
|
+
import { Injectable as Injectable3 } from "@nestjs/common";
|
|
422
|
+
import { RESOLVER_TYPE_METADATA } from "@nestjs/graphql";
|
|
423
|
+
var ResolverDiscoveryService = class {
|
|
424
|
+
constructor(discoveryService) {
|
|
425
|
+
this.discoveryService = discoveryService;
|
|
426
|
+
}
|
|
427
|
+
explore() {
|
|
428
|
+
const wrappedProviders = this.discoveryService.getProviders();
|
|
429
|
+
return wrappedProviders.flatMap((wrapped) => {
|
|
430
|
+
const { instance } = wrapped;
|
|
431
|
+
if (!instance) return [];
|
|
432
|
+
const isResolver = Reflect.hasMetadata(
|
|
433
|
+
RESOLVER_TYPE_METADATA,
|
|
434
|
+
instance.constructor
|
|
435
|
+
);
|
|
436
|
+
if (!isResolver) return [];
|
|
437
|
+
return wrapped;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
ResolverDiscoveryService = __decorateClass([
|
|
442
|
+
Injectable3()
|
|
443
|
+
], ResolverDiscoveryService);
|
|
444
|
+
|
|
445
|
+
// src/modules/core/annotation.module.ts
|
|
446
|
+
var GraphQLAnnotationModule = class {
|
|
447
|
+
static forRoot(opts) {
|
|
448
|
+
if (opts?.serveAnnotations)
|
|
449
|
+
return {
|
|
450
|
+
global: true,
|
|
451
|
+
module: GraphQLAnnotationModule,
|
|
452
|
+
imports: [DiscoveryModule],
|
|
453
|
+
providers: [
|
|
454
|
+
ResolverDiscoveryService,
|
|
455
|
+
GraphQLAnnotatedSchemaLoader,
|
|
456
|
+
{
|
|
457
|
+
provide: ANNOTATION_RESOLVER_METADATA,
|
|
458
|
+
useValue: opts.serveConfig
|
|
459
|
+
},
|
|
460
|
+
GraphQLAnnotationResolver
|
|
461
|
+
],
|
|
462
|
+
exports: [GraphQLAnnotatedSchemaLoader]
|
|
463
|
+
};
|
|
464
|
+
return {
|
|
465
|
+
global: true,
|
|
466
|
+
module: GraphQLAnnotationModule,
|
|
467
|
+
imports: [DiscoveryModule],
|
|
468
|
+
providers: [ResolverDiscoveryService, GraphQLAnnotatedSchemaLoader],
|
|
469
|
+
exports: [GraphQLAnnotatedSchemaLoader]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
GraphQLAnnotationModule = __decorateClass([
|
|
474
|
+
Module({})
|
|
475
|
+
], GraphQLAnnotationModule);
|
|
476
|
+
export {
|
|
477
|
+
ANNOTATION_METADATA,
|
|
478
|
+
ANNOTATION_RESOLVER_METADATA,
|
|
479
|
+
Annotate,
|
|
480
|
+
GRAPHQL_ANNOTATION_RESOLVER_METADATA,
|
|
481
|
+
GraphQLAnnotatedSchemaLoader,
|
|
482
|
+
GraphQLAnnotationModule,
|
|
483
|
+
ResolveAnnotation
|
|
484
|
+
};
|
package/nest-cli.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tinkerstack/graphql-annotation",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Better type-safe-able graphql directives",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"author": "@plasteek",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@graphql-tools/delegate": "^12.0.4",
|
|
13
|
+
"@graphql-tools/executor-http": "^3.1.0",
|
|
14
|
+
"@graphql-tools/stitch": "^10.1.8",
|
|
15
|
+
"@graphql-tools/utils": "^11.0.0",
|
|
16
|
+
"@graphql-tools/wrap": "^11.1.4",
|
|
17
|
+
"class-transformer": "^0.5.1",
|
|
18
|
+
"graphql-request": "^7.2.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@nestjs/common": "^10 || ^11",
|
|
22
|
+
"@nestjs/core": "^10 || ^11",
|
|
23
|
+
"@nestjs/graphql": "^13",
|
|
24
|
+
"class-transformer": "^0",
|
|
25
|
+
"class-validator": "^0",
|
|
26
|
+
"graphql": "^16",
|
|
27
|
+
"graphql-scalars": "^1",
|
|
28
|
+
"reflect-metadata": "^0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@apollo/server": "^4.12.2",
|
|
32
|
+
"@as-integrations/fastify": "^3.1.0",
|
|
33
|
+
"@nestjs/apollo": "^13.2.3",
|
|
34
|
+
"@nestjs/common": "^11.1.6",
|
|
35
|
+
"@nestjs/core": "^11.1.3",
|
|
36
|
+
"@nestjs/graphql": "^13.2.3",
|
|
37
|
+
"@nestjs/mapped-types": "^2.1.0",
|
|
38
|
+
"@nestjs/platform-fastify": "^11.1.11",
|
|
39
|
+
"class-validator": "^0.14.2",
|
|
40
|
+
"concurrently": "^9.2.1",
|
|
41
|
+
"eslint": "^9.37.0",
|
|
42
|
+
"graphql": "^16.12.0",
|
|
43
|
+
"graphql-scalars": "^1.24.2",
|
|
44
|
+
"prettier": "^3.6.2",
|
|
45
|
+
"reflect-metadata": "^0.2.2",
|
|
46
|
+
"tslib": "^2.8.1",
|
|
47
|
+
"tsup": "^8.5.0",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "nest start --watch"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +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 {}
|
|
@@ -0,0 +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 {}
|
|
@@ -0,0 +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();
|
|
@@ -0,0 +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 {}
|