@omnigraph/grpc 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/cjs/directives.js +72 -0
- package/cjs/gRPCLoader.js +465 -0
- package/cjs/index.js +10 -0
- package/cjs/package.json +1 -0
- package/cjs/scalars.js +31 -0
- package/cjs/utils.js +52 -0
- package/esm/directives.js +69 -0
- package/esm/gRPCLoader.js +460 -0
- package/esm/index.js +7 -0
- package/esm/scalars.js +27 -0
- package/esm/utils.js +46 -0
- package/package.json +54 -0
- package/typings/directives.d.cts +5 -0
- package/typings/directives.d.ts +5 -0
- package/typings/gRPCLoader.d.cts +28 -0
- package/typings/gRPCLoader.d.ts +28 -0
- package/typings/index.d.cts +10 -0
- package/typings/index.d.ts +10 -0
- package/typings/scalars.d.cts +2 -0
- package/typings/scalars.d.ts +2 -0
- package/typings/utils.d.cts +5 -0
- package/typings/utils.d.ts +5 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DirectiveLocation, GraphQLBoolean, GraphQLDirective, GraphQLString, } from 'graphql';
|
|
2
|
+
import { ObjMapScalar } from '@graphql-mesh/transport-common';
|
|
3
|
+
export const grpcMethodDirective = new GraphQLDirective({
|
|
4
|
+
name: 'grpcMethod',
|
|
5
|
+
locations: [DirectiveLocation.FIELD_DEFINITION],
|
|
6
|
+
args: {
|
|
7
|
+
subgraph: {
|
|
8
|
+
type: GraphQLString,
|
|
9
|
+
},
|
|
10
|
+
rootJsonName: {
|
|
11
|
+
type: GraphQLString,
|
|
12
|
+
},
|
|
13
|
+
objPath: {
|
|
14
|
+
type: GraphQLString,
|
|
15
|
+
},
|
|
16
|
+
methodName: {
|
|
17
|
+
type: GraphQLString,
|
|
18
|
+
},
|
|
19
|
+
responseStream: {
|
|
20
|
+
type: GraphQLBoolean,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
export const grpcConnectivityStateDirective = new GraphQLDirective({
|
|
25
|
+
name: 'grpcConnectivityState',
|
|
26
|
+
locations: [DirectiveLocation.FIELD_DEFINITION],
|
|
27
|
+
args: {
|
|
28
|
+
subgraph: {
|
|
29
|
+
type: GraphQLString,
|
|
30
|
+
},
|
|
31
|
+
rootJsonName: {
|
|
32
|
+
type: GraphQLString,
|
|
33
|
+
},
|
|
34
|
+
objPath: {
|
|
35
|
+
type: GraphQLString,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
export const EnumDirective = new GraphQLDirective({
|
|
40
|
+
name: 'enum',
|
|
41
|
+
locations: [DirectiveLocation.ENUM_VALUE],
|
|
42
|
+
args: {
|
|
43
|
+
subgraph: {
|
|
44
|
+
type: GraphQLString,
|
|
45
|
+
},
|
|
46
|
+
value: {
|
|
47
|
+
type: GraphQLString,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
export const grpcRootJsonDirective = new GraphQLDirective({
|
|
52
|
+
name: 'grpcRootJson',
|
|
53
|
+
locations: [DirectiveLocation.OBJECT],
|
|
54
|
+
args: {
|
|
55
|
+
subgraph: {
|
|
56
|
+
type: GraphQLString,
|
|
57
|
+
},
|
|
58
|
+
name: {
|
|
59
|
+
type: GraphQLString,
|
|
60
|
+
},
|
|
61
|
+
rootJson: {
|
|
62
|
+
type: ObjMapScalar,
|
|
63
|
+
},
|
|
64
|
+
loadOptions: {
|
|
65
|
+
type: ObjMapScalar,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
isRepeatable: true,
|
|
69
|
+
});
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { specifiedDirectives } from "graphql";
|
|
2
|
+
import { SchemaComposer } from "graphql-compose";
|
|
3
|
+
import { GraphQLBigInt, GraphQLByte, GraphQLUnsignedInt, GraphQLVoid, GraphQLJSON } from "graphql-scalars";
|
|
4
|
+
import { EnumDirective, grpcConnectivityStateDirective, grpcMethodDirective, grpcRootJsonDirective } from "./directives.js";
|
|
5
|
+
import { credentials } from '@grpc/grpc-js';
|
|
6
|
+
import protobufjs from 'protobufjs';
|
|
7
|
+
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
|
|
8
|
+
import { isAbsolute, join } from "node:path";
|
|
9
|
+
import { fs } from "@graphql-mesh/cross-helpers";
|
|
10
|
+
import globby from "globby";
|
|
11
|
+
import { addIncludePathResolver, getTypeName, walkToFindTypePath } from "./utils.js";
|
|
12
|
+
import { GraphQLStreamDirective } from "@graphql-tools/utils";
|
|
13
|
+
import descriptor from 'protobufjs/ext/descriptor/index.js';
|
|
14
|
+
import { Client } from '@ardatan/grpc-reflection-js';
|
|
15
|
+
import { AsyncDisposableStack } from '@whatwg-node/disposablestack';
|
|
16
|
+
const { Root } = protobufjs;
|
|
17
|
+
const QUERY_METHOD_PREFIXES = ['get', 'list', 'search'];
|
|
18
|
+
export class gRPCLoader {
|
|
19
|
+
constructor(subgraphName, baseDir, logger, config) {
|
|
20
|
+
this.subgraphName = subgraphName;
|
|
21
|
+
this.baseDir = baseDir;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.schemaComposer = new SchemaComposer();
|
|
25
|
+
this.asyncDisposableStack = new AsyncDisposableStack;
|
|
26
|
+
}
|
|
27
|
+
[Symbol.asyncDispose]() {
|
|
28
|
+
return this.asyncDisposableStack.disposeAsync();
|
|
29
|
+
}
|
|
30
|
+
async buildSchema() {
|
|
31
|
+
this.schemaComposer.add(GraphQLBigInt);
|
|
32
|
+
this.schemaComposer.add(GraphQLByte);
|
|
33
|
+
this.schemaComposer.add(GraphQLUnsignedInt);
|
|
34
|
+
this.schemaComposer.add(GraphQLVoid);
|
|
35
|
+
this.schemaComposer.add(GraphQLJSON);
|
|
36
|
+
this.schemaComposer.createScalarTC({
|
|
37
|
+
name: 'File',
|
|
38
|
+
});
|
|
39
|
+
// identical of grpc's ConnectivityState
|
|
40
|
+
this.schemaComposer.createEnumTC({
|
|
41
|
+
name: 'ConnectivityState',
|
|
42
|
+
values: {
|
|
43
|
+
IDLE: { value: 0 },
|
|
44
|
+
CONNECTING: { value: 1 },
|
|
45
|
+
READY: { value: 2 },
|
|
46
|
+
TRANSIENT_FAILURE: { value: 3 },
|
|
47
|
+
SHUTDOWN: { value: 4 },
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
this.config.requestTimeout = this.config.requestTimeout || 200000;
|
|
51
|
+
this.logger.debug(`Getting channel credentials`);
|
|
52
|
+
const creds = await this.getCredentials();
|
|
53
|
+
this.logger.debug(`Getting stored root and decoded descriptor set objects`);
|
|
54
|
+
const descriptorSets = await this.getDescriptorSets(creds);
|
|
55
|
+
const directives = [];
|
|
56
|
+
for (const { name: rootJsonName, rootJson } of descriptorSets) {
|
|
57
|
+
const rootLogger = this.logger.child(rootJsonName);
|
|
58
|
+
this.logger.debug(`Building the schema structure based on the root object`);
|
|
59
|
+
this.visit({
|
|
60
|
+
nested: rootJson,
|
|
61
|
+
name: '',
|
|
62
|
+
currentPath: [],
|
|
63
|
+
rootJsonName,
|
|
64
|
+
rootJson,
|
|
65
|
+
rootLogger,
|
|
66
|
+
});
|
|
67
|
+
this.schemaComposer.addDirective(grpcRootJsonDirective);
|
|
68
|
+
directives.push({
|
|
69
|
+
name: 'grpcRootJson',
|
|
70
|
+
args: {
|
|
71
|
+
subgraph: this.subgraphName,
|
|
72
|
+
name: rootJsonName,
|
|
73
|
+
rootJson,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
this.schemaComposer.Query.setDirectives(directives);
|
|
78
|
+
// graphql-compose doesn't add @defer and @stream to the schema
|
|
79
|
+
specifiedDirectives.forEach(directive => this.schemaComposer.addDirective(directive));
|
|
80
|
+
if (!this.schemaComposer.hasDirective('stream')) {
|
|
81
|
+
this.schemaComposer.addDirective(GraphQLStreamDirective);
|
|
82
|
+
}
|
|
83
|
+
this.logger.debug(`Building the final GraphQL Schema`);
|
|
84
|
+
const schema = this.schemaComposer.buildSchema();
|
|
85
|
+
const schemaExtensions = schema.extensions = schema.extensions || {};
|
|
86
|
+
const directiveExtensions = schemaExtensions.directives = schemaExtensions.directives || {};
|
|
87
|
+
directiveExtensions.transport = {
|
|
88
|
+
subgraph: this.subgraphName,
|
|
89
|
+
kind: 'grpc',
|
|
90
|
+
location: this.config.endpoint,
|
|
91
|
+
options: {
|
|
92
|
+
requestTimeout: this.config.requestTimeout,
|
|
93
|
+
credentialsSsl: this.config.credentialsSsl,
|
|
94
|
+
useHTTPS: this.config.useHTTPS,
|
|
95
|
+
metaData: this.config.metaData,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
return schema;
|
|
99
|
+
}
|
|
100
|
+
processReflection(creds) {
|
|
101
|
+
this.logger.debug(`Using the reflection`);
|
|
102
|
+
const reflectionEndpoint = stringInterpolator.parse(this.config.endpoint, { env: process.env });
|
|
103
|
+
this.logger.debug(`Creating gRPC Reflection Client`);
|
|
104
|
+
const reflectionClient = new Client(reflectionEndpoint, creds);
|
|
105
|
+
this.asyncDisposableStack.defer(() => reflectionClient.grpcClient.close());
|
|
106
|
+
return reflectionClient.listServices().then(services => (services.filter(service => service && !service?.startsWith('grpc.'))
|
|
107
|
+
.map(service => {
|
|
108
|
+
this.logger.debug(`Resolving root of Service: ${service} from the reflection response`);
|
|
109
|
+
return reflectionClient.fileContainingSymbol(service);
|
|
110
|
+
})));
|
|
111
|
+
}
|
|
112
|
+
async processDescriptorFile() {
|
|
113
|
+
let fileName;
|
|
114
|
+
let options;
|
|
115
|
+
if (typeof this.config.source === 'object') {
|
|
116
|
+
fileName = this.config.source.file;
|
|
117
|
+
options = {
|
|
118
|
+
...this.config.source.load,
|
|
119
|
+
includeDirs: this.config.source.load.includeDirs?.map(includeDir => isAbsolute(includeDir) ? includeDir : join(this.baseDir, includeDir)),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
fileName = this.config.source;
|
|
124
|
+
}
|
|
125
|
+
fileName = stringInterpolator.parse(fileName, { env: process.env });
|
|
126
|
+
const absoluteFilePath = isAbsolute(fileName)
|
|
127
|
+
? fileName
|
|
128
|
+
: join(this.baseDir, fileName);
|
|
129
|
+
this.logger.debug(`Using the descriptor set from ${absoluteFilePath} `);
|
|
130
|
+
const descriptorSetBuffer = await fs.promises.readFile(absoluteFilePath);
|
|
131
|
+
this.logger.debug(`Reading ${absoluteFilePath} `);
|
|
132
|
+
let decodedDescriptorSet;
|
|
133
|
+
if (absoluteFilePath.endsWith('json')) {
|
|
134
|
+
this.logger.debug(`Parsing ${absoluteFilePath} as json`);
|
|
135
|
+
const descriptorSetJSON = JSON.parse(descriptorSetBuffer.toString());
|
|
136
|
+
decodedDescriptorSet = descriptor.FileDescriptorSet.fromObject(descriptorSetJSON);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
decodedDescriptorSet = descriptor.FileDescriptorSet.decode(descriptorSetBuffer);
|
|
140
|
+
}
|
|
141
|
+
this.logger.debug(`Creating root from descriptor set`);
|
|
142
|
+
const rootFromDescriptor = Root.fromDescriptor(decodedDescriptorSet);
|
|
143
|
+
if (options.includeDirs) {
|
|
144
|
+
if (!Array.isArray(options.includeDirs)) {
|
|
145
|
+
return Promise.reject(new Error('The includeDirs option must be an array'));
|
|
146
|
+
}
|
|
147
|
+
addIncludePathResolver(rootFromDescriptor, options.includeDirs);
|
|
148
|
+
}
|
|
149
|
+
return rootFromDescriptor;
|
|
150
|
+
}
|
|
151
|
+
async processProtoFile() {
|
|
152
|
+
this.logger.debug(`Using proto file(s)`);
|
|
153
|
+
let protoRoot = new Root();
|
|
154
|
+
let fileGlob;
|
|
155
|
+
let options = {
|
|
156
|
+
keepCase: true,
|
|
157
|
+
alternateCommentMode: true,
|
|
158
|
+
};
|
|
159
|
+
if (typeof this.config.source === 'object') {
|
|
160
|
+
fileGlob = this.config.source.file;
|
|
161
|
+
options = {
|
|
162
|
+
...options,
|
|
163
|
+
...this.config.source.load,
|
|
164
|
+
includeDirs: this.config.source.load?.includeDirs?.map(includeDir => isAbsolute(includeDir) ? includeDir : join(this.baseDir, includeDir)),
|
|
165
|
+
};
|
|
166
|
+
if (options.includeDirs) {
|
|
167
|
+
if (!Array.isArray(options.includeDirs)) {
|
|
168
|
+
throw new Error('The includeDirs option must be an array');
|
|
169
|
+
}
|
|
170
|
+
addIncludePathResolver(protoRoot, options.includeDirs);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
fileGlob = this.config.source;
|
|
175
|
+
}
|
|
176
|
+
fileGlob = stringInterpolator.parse(fileGlob, { env: process.env });
|
|
177
|
+
const fileNames = await globby(fileGlob, {
|
|
178
|
+
cwd: this.baseDir,
|
|
179
|
+
});
|
|
180
|
+
this.logger.debug(`Loading proto files(${fileGlob}); \n ${fileNames.join('\n')} `);
|
|
181
|
+
protoRoot = await protoRoot.load(fileNames.map(filePath => isAbsolute(filePath) ? filePath : join(this.baseDir, filePath)), options);
|
|
182
|
+
this.logger.debug(`Adding proto content to the root`);
|
|
183
|
+
return protoRoot;
|
|
184
|
+
}
|
|
185
|
+
async getDescriptorSets(creds) {
|
|
186
|
+
const rootPromises = [];
|
|
187
|
+
this.logger.debug(`Building Roots`);
|
|
188
|
+
if (this.config.source) {
|
|
189
|
+
const filePath = typeof this.config.source === 'string' ? this.config.source : this.config.source.file;
|
|
190
|
+
if (filePath.endsWith('json')) {
|
|
191
|
+
rootPromises.push(this.processDescriptorFile());
|
|
192
|
+
}
|
|
193
|
+
else if (filePath.endsWith('proto')) {
|
|
194
|
+
rootPromises.push(this.processProtoFile());
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const reflectionPromises = await this.processReflection(creds);
|
|
199
|
+
rootPromises.push(...reflectionPromises);
|
|
200
|
+
}
|
|
201
|
+
return Promise.all(rootPromises.map(async (root$, i) => {
|
|
202
|
+
const root = await root$;
|
|
203
|
+
const rootName = root.name || `Root${i}`;
|
|
204
|
+
const rootLogger = this.logger.child(rootName);
|
|
205
|
+
rootLogger.debug(`Resolving entire the root tree`);
|
|
206
|
+
root.resolveAll();
|
|
207
|
+
rootLogger.debug(`Creating artifacts from descriptor set and root`);
|
|
208
|
+
return {
|
|
209
|
+
name: rootName,
|
|
210
|
+
rootJson: root.toJSON({
|
|
211
|
+
keepComments: true,
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
getCredentials() {
|
|
217
|
+
this.logger.debug(`Getting channel credentials`);
|
|
218
|
+
if (this.config.credentialsSsl) {
|
|
219
|
+
this.logger.debug(() => `Using SSL Connection with credentials at ${this.config.credentialsSsl.privateKey} & ${this.config.credentialsSsl.certChain}`);
|
|
220
|
+
const absolutePrivateKeyPath = isAbsolute(this.config.credentialsSsl.privateKey)
|
|
221
|
+
? this.config.credentialsSsl.privateKey
|
|
222
|
+
: join(this.baseDir, this.config.credentialsSsl.privateKey);
|
|
223
|
+
const absoluteCertChainPath = isAbsolute(this.config.credentialsSsl.certChain)
|
|
224
|
+
? this.config.credentialsSsl.certChain
|
|
225
|
+
: join(this.baseDir, this.config.credentialsSsl.certChain);
|
|
226
|
+
const sslFiles = [
|
|
227
|
+
fs.promises.readFile(absolutePrivateKeyPath),
|
|
228
|
+
fs.promises.readFile(absoluteCertChainPath),
|
|
229
|
+
];
|
|
230
|
+
if (this.config.credentialsSsl.rootCA !== 'rootCA') {
|
|
231
|
+
const absoluteRootCAPath = isAbsolute(this.config.credentialsSsl.rootCA)
|
|
232
|
+
? this.config.credentialsSsl.rootCA
|
|
233
|
+
: join(this.baseDir, this.config.credentialsSsl.rootCA);
|
|
234
|
+
sslFiles.unshift(fs.promises.readFile(absoluteRootCAPath));
|
|
235
|
+
}
|
|
236
|
+
return Promise.all(sslFiles).then(([rootCA, privateKey, certChain]) => credentials.createSsl(rootCA, privateKey, certChain));
|
|
237
|
+
}
|
|
238
|
+
else if (this.config.useHTTPS) {
|
|
239
|
+
this.logger.debug(`Using SSL Connection`);
|
|
240
|
+
return credentials.createSsl();
|
|
241
|
+
}
|
|
242
|
+
this.logger.debug(`Using insecure connection`);
|
|
243
|
+
return credentials.createInsecure();
|
|
244
|
+
}
|
|
245
|
+
visit({ nested, name, currentPath, rootJsonName, rootJson, rootLogger: logger, }) {
|
|
246
|
+
const pathWithName = [...currentPath, ...name.split('.')].filter(Boolean);
|
|
247
|
+
if ('nested' in nested) {
|
|
248
|
+
for (const key in nested.nested) {
|
|
249
|
+
logger.debug(`Visiting ${currentPath}.nested[${key}]`);
|
|
250
|
+
const currentNested = nested.nested[key];
|
|
251
|
+
this.visit({
|
|
252
|
+
nested: currentNested,
|
|
253
|
+
name: key,
|
|
254
|
+
currentPath: pathWithName,
|
|
255
|
+
rootJsonName,
|
|
256
|
+
rootJson,
|
|
257
|
+
rootLogger: logger,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const typeName = pathWithName.join('__');
|
|
262
|
+
if ('values' in nested) {
|
|
263
|
+
const enumValues = {};
|
|
264
|
+
const commentMap = nested.comments;
|
|
265
|
+
for (const [key, value] of Object.entries(nested.values)) {
|
|
266
|
+
logger.debug(`Visiting ${currentPath}.nested.values[${key}]`);
|
|
267
|
+
enumValues[key] = {
|
|
268
|
+
directives: [
|
|
269
|
+
{
|
|
270
|
+
name: 'enum',
|
|
271
|
+
args: {
|
|
272
|
+
subgraph: this.subgraphName,
|
|
273
|
+
value: JSON.stringify(value),
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
description: commentMap?.[key],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
this.schemaComposer.addDirective(EnumDirective);
|
|
281
|
+
this.schemaComposer.createEnumTC({
|
|
282
|
+
name: typeName,
|
|
283
|
+
values: enumValues,
|
|
284
|
+
description: nested.comment,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
else if ('fields' in nested) {
|
|
288
|
+
const inputTypeName = typeName + '_Input';
|
|
289
|
+
const outputTypeName = typeName;
|
|
290
|
+
const description = nested.comment;
|
|
291
|
+
const fieldEntries = Object.entries(nested.fields);
|
|
292
|
+
if (fieldEntries.length) {
|
|
293
|
+
const inputTC = this.schemaComposer.createInputTC({
|
|
294
|
+
name: inputTypeName,
|
|
295
|
+
description,
|
|
296
|
+
fields: {},
|
|
297
|
+
});
|
|
298
|
+
const outputTC = this.schemaComposer.createObjectTC({
|
|
299
|
+
name: outputTypeName,
|
|
300
|
+
description,
|
|
301
|
+
fields: {},
|
|
302
|
+
});
|
|
303
|
+
for (const [fieldName, { type, rule, comment, keyType }] of fieldEntries) {
|
|
304
|
+
logger.debug(`Visiting ${currentPath}.nested.fields[${fieldName}]`);
|
|
305
|
+
const baseFieldTypePath = type.split('.');
|
|
306
|
+
inputTC.addFields({
|
|
307
|
+
[fieldName]: {
|
|
308
|
+
type: () => {
|
|
309
|
+
let fieldInputTypeName;
|
|
310
|
+
if (keyType) {
|
|
311
|
+
fieldInputTypeName = 'JSON';
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
const fieldTypePath = walkToFindTypePath(rootJson, pathWithName, baseFieldTypePath);
|
|
315
|
+
fieldInputTypeName = getTypeName(this.schemaComposer, fieldTypePath, true);
|
|
316
|
+
}
|
|
317
|
+
return rule === 'repeated' ? `[${fieldInputTypeName}]` : fieldInputTypeName;
|
|
318
|
+
},
|
|
319
|
+
description: comment,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
outputTC.addFields({
|
|
323
|
+
[fieldName]: {
|
|
324
|
+
type: () => {
|
|
325
|
+
let fieldTypeName;
|
|
326
|
+
if (keyType) {
|
|
327
|
+
fieldTypeName = 'JSON';
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const fieldTypePath = walkToFindTypePath(rootJson, pathWithName, baseFieldTypePath);
|
|
331
|
+
fieldTypeName = getTypeName(this.schemaComposer, fieldTypePath, false);
|
|
332
|
+
}
|
|
333
|
+
return rule === 'repeated' ? `[${fieldTypeName}]` : fieldTypeName;
|
|
334
|
+
},
|
|
335
|
+
description: comment,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
this.schemaComposer.createScalarTC({
|
|
342
|
+
...GraphQLJSON.toConfig(),
|
|
343
|
+
name: inputTypeName,
|
|
344
|
+
description,
|
|
345
|
+
});
|
|
346
|
+
this.schemaComposer.createScalarTC({
|
|
347
|
+
...GraphQLJSON.toConfig(),
|
|
348
|
+
name: outputTypeName,
|
|
349
|
+
description,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else if ('methods' in nested) {
|
|
354
|
+
const objPath = pathWithName.join('.');
|
|
355
|
+
for (const methodName in nested.methods) {
|
|
356
|
+
const method = nested.methods[methodName];
|
|
357
|
+
const rootFieldName = [...pathWithName, methodName].join('_');
|
|
358
|
+
const fieldConfigTypeFactory = () => {
|
|
359
|
+
const baseResponseTypePath = method.responseType?.split('.');
|
|
360
|
+
if (baseResponseTypePath) {
|
|
361
|
+
const responseTypePath = walkToFindTypePath(rootJson, pathWithName, baseResponseTypePath);
|
|
362
|
+
return getTypeName(this.schemaComposer, responseTypePath, false);
|
|
363
|
+
}
|
|
364
|
+
return 'Void';
|
|
365
|
+
};
|
|
366
|
+
const fieldConfig = {
|
|
367
|
+
type: () => {
|
|
368
|
+
const typeName = fieldConfigTypeFactory();
|
|
369
|
+
if (method.responseStream) {
|
|
370
|
+
return `[${typeName}]`;
|
|
371
|
+
}
|
|
372
|
+
return typeName;
|
|
373
|
+
},
|
|
374
|
+
description: method.comment,
|
|
375
|
+
};
|
|
376
|
+
const fieldConfigArgs = {
|
|
377
|
+
input: () => {
|
|
378
|
+
if (method.requestStream) {
|
|
379
|
+
return 'File';
|
|
380
|
+
}
|
|
381
|
+
const baseRequestTypePath = method.requestType?.split('.');
|
|
382
|
+
if (baseRequestTypePath) {
|
|
383
|
+
const requestTypePath = walkToFindTypePath(rootJson, pathWithName, baseRequestTypePath);
|
|
384
|
+
const requestTypeName = getTypeName(this.schemaComposer, requestTypePath, true);
|
|
385
|
+
return requestTypeName;
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
fieldConfig.args = fieldConfigArgs;
|
|
391
|
+
const methodNameLowerCased = methodName.toLowerCase();
|
|
392
|
+
const prefixQueryMethod = this.config.prefixQueryMethod || QUERY_METHOD_PREFIXES;
|
|
393
|
+
const rootTypeComposer = prefixQueryMethod.some(prefix => methodNameLowerCased.startsWith(prefix))
|
|
394
|
+
? this.schemaComposer.Query
|
|
395
|
+
: this.schemaComposer.Mutation;
|
|
396
|
+
this.schemaComposer.addDirective(grpcMethodDirective);
|
|
397
|
+
rootTypeComposer.addFields({
|
|
398
|
+
[rootFieldName]: {
|
|
399
|
+
...fieldConfig,
|
|
400
|
+
directives: [
|
|
401
|
+
{
|
|
402
|
+
name: 'grpcMethod',
|
|
403
|
+
args: {
|
|
404
|
+
subgraph: this.subgraphName,
|
|
405
|
+
rootJsonName,
|
|
406
|
+
objPath,
|
|
407
|
+
methodName,
|
|
408
|
+
responseStream: !!method.responseStream,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
if (method.responseStream) {
|
|
415
|
+
this.schemaComposer.Subscription.addFields({
|
|
416
|
+
[rootFieldName]: {
|
|
417
|
+
args: fieldConfigArgs,
|
|
418
|
+
description: method.comment,
|
|
419
|
+
type: fieldConfigTypeFactory,
|
|
420
|
+
directives: [
|
|
421
|
+
{
|
|
422
|
+
name: 'grpcMethod',
|
|
423
|
+
args: {
|
|
424
|
+
subgraph: this.subgraphName,
|
|
425
|
+
rootJsonName,
|
|
426
|
+
objPath,
|
|
427
|
+
methodName,
|
|
428
|
+
responseStream: true,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const connectivityStateFieldName = pathWithName.join('_') + '_connectivityState';
|
|
437
|
+
this.schemaComposer.addDirective(grpcConnectivityStateDirective);
|
|
438
|
+
this.schemaComposer.Query.addFields({
|
|
439
|
+
[connectivityStateFieldName]: {
|
|
440
|
+
type: 'ConnectivityState',
|
|
441
|
+
args: {
|
|
442
|
+
tryToConnect: {
|
|
443
|
+
type: 'Boolean',
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
directives: [
|
|
447
|
+
{
|
|
448
|
+
name: 'grpcConnectivityState',
|
|
449
|
+
args: {
|
|
450
|
+
subgraph: this.subgraphName,
|
|
451
|
+
rootJsonName,
|
|
452
|
+
objPath,
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
package/esm/index.js
ADDED
package/esm/scalars.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const SCALARS = new Map([
|
|
2
|
+
['bool', 'Boolean'],
|
|
3
|
+
['bytes', 'Byte'],
|
|
4
|
+
['double', 'Float'],
|
|
5
|
+
['fixed32', 'Int'],
|
|
6
|
+
['fixed64', 'BigInt'],
|
|
7
|
+
['float', 'Float'],
|
|
8
|
+
['int32', 'Int'],
|
|
9
|
+
['int64', 'BigInt'],
|
|
10
|
+
['sfixed32', 'Int'],
|
|
11
|
+
['sfixed64', 'BigInt'],
|
|
12
|
+
['sint32', 'Int'],
|
|
13
|
+
['sint64', 'BigInt'],
|
|
14
|
+
['string', 'String'],
|
|
15
|
+
['uint32', 'UnsignedInt'],
|
|
16
|
+
['uint64', 'BigInt'], // A new scalar might be needed
|
|
17
|
+
]);
|
|
18
|
+
export function isGrpcScalar(type) {
|
|
19
|
+
return SCALARS.has(type);
|
|
20
|
+
}
|
|
21
|
+
export function getGraphQLScalarForGrpc(scalarType) {
|
|
22
|
+
const gqlScalar = SCALARS.get(scalarType);
|
|
23
|
+
if (!gqlScalar) {
|
|
24
|
+
throw new Error(`Could not find GraphQL Scalar for type ${scalarType}`);
|
|
25
|
+
}
|
|
26
|
+
return SCALARS.get(scalarType);
|
|
27
|
+
}
|
package/esm/utils.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { isAbsolute, join } from "path";
|
|
3
|
+
import { getGraphQLScalarForGrpc, isGrpcScalar } from "./scalars.js";
|
|
4
|
+
import lodashHas from 'lodash.has';
|
|
5
|
+
export function addIncludePathResolver(root, includePaths) {
|
|
6
|
+
const originalResolvePath = root.resolvePath;
|
|
7
|
+
root.resolvePath = (origin, target) => {
|
|
8
|
+
if (isAbsolute(target)) {
|
|
9
|
+
return target;
|
|
10
|
+
}
|
|
11
|
+
for (const directory of includePaths) {
|
|
12
|
+
const fullPath = join(directory, target);
|
|
13
|
+
if (existsSync(fullPath)) {
|
|
14
|
+
return fullPath;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const path = originalResolvePath(origin, target);
|
|
18
|
+
if (path === null) {
|
|
19
|
+
console.warn(`${target} not found in any of the include paths ${includePaths}`);
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function getTypeName(schemaComposer, pathWithName, isInput) {
|
|
25
|
+
if (pathWithName?.length) {
|
|
26
|
+
const baseTypeName = pathWithName.filter(Boolean).join('__');
|
|
27
|
+
if (isGrpcScalar(baseTypeName)) {
|
|
28
|
+
return getGraphQLScalarForGrpc(baseTypeName);
|
|
29
|
+
}
|
|
30
|
+
if (schemaComposer.isEnumType(baseTypeName)) {
|
|
31
|
+
return baseTypeName;
|
|
32
|
+
}
|
|
33
|
+
return isInput ? baseTypeName + '_Input' : baseTypeName;
|
|
34
|
+
}
|
|
35
|
+
return 'Void';
|
|
36
|
+
}
|
|
37
|
+
export function walkToFindTypePath(rootJson, pathWithName, baseTypePath) {
|
|
38
|
+
const currentWalkingPath = [...pathWithName];
|
|
39
|
+
while (!lodashHas(rootJson.nested, currentWalkingPath.concat(baseTypePath).join('.nested.'))) {
|
|
40
|
+
if (!currentWalkingPath.length) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
currentWalkingPath.pop();
|
|
44
|
+
}
|
|
45
|
+
return currentWalkingPath.concat(baseTypePath);
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omnigraph/grpc",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"graphql": "*"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@ardatan/grpc-reflection-js": "^0.0.2",
|
|
10
|
+
"@graphql-mesh/string-interpolation": "^0.5.6",
|
|
11
|
+
"@graphql-mesh/transport-common": "^0.7.11",
|
|
12
|
+
"@graphql-tools/utils": "^10.5.5",
|
|
13
|
+
"@grpc/grpc-js": "^1.1.7",
|
|
14
|
+
"@whatwg-node/disposablestack": "^0.0.5",
|
|
15
|
+
"globby": "^11.1.0",
|
|
16
|
+
"graphql-compose": "^9.0.11",
|
|
17
|
+
"graphql-scalars": "^1.23.0",
|
|
18
|
+
"lodash.has": "^4.5.2",
|
|
19
|
+
"protobufjs": "^7.2.5"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "ardatan/graphql-mesh",
|
|
24
|
+
"directory": "packages/loaders/grpc"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16.0.0"
|
|
29
|
+
},
|
|
30
|
+
"main": "cjs/index.js",
|
|
31
|
+
"module": "esm/index.js",
|
|
32
|
+
"typings": "typings/index.d.ts",
|
|
33
|
+
"typescript": {
|
|
34
|
+
"definition": "typings/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"type": "module",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./typings/index.d.cts",
|
|
41
|
+
"default": "./cjs/index.js"
|
|
42
|
+
},
|
|
43
|
+
"import": {
|
|
44
|
+
"types": "./typings/index.d.ts",
|
|
45
|
+
"default": "./esm/index.js"
|
|
46
|
+
},
|
|
47
|
+
"default": {
|
|
48
|
+
"types": "./typings/index.d.ts",
|
|
49
|
+
"default": "./esm/index.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"./package.json": "./package.json"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { GraphQLDirective } from 'graphql';
|
|
2
|
+
export declare const grpcMethodDirective: GraphQLDirective;
|
|
3
|
+
export declare const grpcConnectivityStateDirective: GraphQLDirective;
|
|
4
|
+
export declare const EnumDirective: GraphQLDirective;
|
|
5
|
+
export declare const grpcRootJsonDirective: GraphQLDirective;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { GraphQLDirective } from 'graphql';
|
|
2
|
+
export declare const grpcMethodDirective: GraphQLDirective;
|
|
3
|
+
export declare const grpcConnectivityStateDirective: GraphQLDirective;
|
|
4
|
+
export declare const EnumDirective: GraphQLDirective;
|
|
5
|
+
export declare const grpcRootJsonDirective: GraphQLDirective;
|