navis.js 5.3.2 → 5.4.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 +198 -5
- package/examples/graphql-demo.js +130 -0
- package/examples/graphql-demo.ts +158 -0
- package/package.json +1 -1
- package/src/graphql/graphql-server.js +362 -0
- package/src/graphql/resolver.js +200 -0
- package/src/graphql/schema.js +188 -0
- package/src/index.js +27 -0
- package/types/index.d.ts +130 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Server for Navis.js
|
|
3
|
+
* Lightweight GraphQL implementation for serverless and microservice architectures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class GraphQLError extends Error {
|
|
7
|
+
constructor(message, code = 'INTERNAL_ERROR', extensions = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'GraphQLError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.extensions = extensions;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GraphQL Server
|
|
17
|
+
*/
|
|
18
|
+
class GraphQLServer {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.schema = options.schema || null;
|
|
21
|
+
this.resolvers = options.resolvers || {};
|
|
22
|
+
this.context = options.context || (() => ({}));
|
|
23
|
+
this.formatError = options.formatError || this._defaultFormatError;
|
|
24
|
+
this.introspection = options.introspection !== false; // Enable by default
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create GraphQL handler middleware
|
|
29
|
+
* @param {Object} options - Handler options
|
|
30
|
+
* @returns {Function} - Middleware function
|
|
31
|
+
*/
|
|
32
|
+
handler(options = {}) {
|
|
33
|
+
const path = options.path || '/graphql';
|
|
34
|
+
const method = options.method || 'POST';
|
|
35
|
+
const enableGET = options.enableGET !== false; // Enable GET by default
|
|
36
|
+
|
|
37
|
+
return async (req, res, next) => {
|
|
38
|
+
// Check if this is a GraphQL request
|
|
39
|
+
const isGraphQLPath = req.path === path || req.url?.startsWith(path);
|
|
40
|
+
if (!isGraphQLPath) {
|
|
41
|
+
return next();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check method
|
|
45
|
+
if (req.method === method || (enableGET && req.method === 'GET')) {
|
|
46
|
+
try {
|
|
47
|
+
// Parse GraphQL request
|
|
48
|
+
const graphqlRequest = await this._parseRequest(req, enableGET);
|
|
49
|
+
|
|
50
|
+
// Execute GraphQL query
|
|
51
|
+
const result = await this._execute(graphqlRequest, req);
|
|
52
|
+
|
|
53
|
+
// Send response
|
|
54
|
+
this._sendResponse(res, result);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this._sendError(res, error);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
next();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse GraphQL request from HTTP request
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
async _parseRequest(req, enableGET) {
|
|
69
|
+
let query = null;
|
|
70
|
+
let variables = {};
|
|
71
|
+
let operationName = null;
|
|
72
|
+
|
|
73
|
+
if (req.method === 'GET') {
|
|
74
|
+
// Parse from query string
|
|
75
|
+
query = req.query.query || req.query.q;
|
|
76
|
+
variables = req.query.variables ? JSON.parse(req.query.variables) : {};
|
|
77
|
+
operationName = req.query.operationName || null;
|
|
78
|
+
} else {
|
|
79
|
+
// Parse from body
|
|
80
|
+
let body = req.body;
|
|
81
|
+
|
|
82
|
+
// If body is string, parse it
|
|
83
|
+
if (typeof body === 'string') {
|
|
84
|
+
try {
|
|
85
|
+
body = JSON.parse(body);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
throw new GraphQLError('Invalid JSON in request body', 'BAD_REQUEST');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
query = body.query;
|
|
92
|
+
variables = body.variables || {};
|
|
93
|
+
operationName = body.operationName || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!query) {
|
|
97
|
+
throw new GraphQLError('GraphQL query is required', 'BAD_REQUEST');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { query, variables, operationName };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Execute GraphQL query
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
async _execute(request, httpReq) {
|
|
108
|
+
const { query, variables, operationName } = request;
|
|
109
|
+
|
|
110
|
+
// Build context
|
|
111
|
+
const context = await this._buildContext(httpReq);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Simple query execution (without full GraphQL parser)
|
|
115
|
+
// This is a lightweight implementation
|
|
116
|
+
const result = await this._executeQuery(query, variables, operationName, context);
|
|
117
|
+
return { data: result, errors: null };
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
data: null,
|
|
121
|
+
errors: [this.formatError(error)],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Execute GraphQL query (simplified implementation)
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
async _executeQuery(query, variables, operationName, context) {
|
|
131
|
+
// Parse operation type and name
|
|
132
|
+
const operation = this._parseOperation(query, operationName);
|
|
133
|
+
|
|
134
|
+
if (!operation) {
|
|
135
|
+
throw new GraphQLError('Invalid GraphQL query', 'BAD_REQUEST');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Execute based on operation type
|
|
139
|
+
if (operation.type === 'query') {
|
|
140
|
+
return await this._executeQueryOperation(operation, variables, context);
|
|
141
|
+
} else if (operation.type === 'mutation') {
|
|
142
|
+
return await this._executeMutationOperation(operation, variables, context);
|
|
143
|
+
} else {
|
|
144
|
+
throw new GraphQLError(`Unsupported operation type: ${operation.type}`, 'BAD_REQUEST');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse GraphQL operation (simplified)
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
_parseOperation(query, operationName) {
|
|
153
|
+
// Simple regex-based parsing (for lightweight implementation)
|
|
154
|
+
// In production, use a proper GraphQL parser like graphql-js
|
|
155
|
+
|
|
156
|
+
const queryMatch = query.match(/^\s*(query|mutation|subscription)\s+(\w+)?/i);
|
|
157
|
+
if (!queryMatch) {
|
|
158
|
+
// Try to find operation by name
|
|
159
|
+
if (operationName) {
|
|
160
|
+
const namedMatch = query.match(new RegExp(`(query|mutation|subscription)\\s+${operationName}`, 'i'));
|
|
161
|
+
if (namedMatch) {
|
|
162
|
+
return {
|
|
163
|
+
type: namedMatch[1].toLowerCase(),
|
|
164
|
+
name: operationName,
|
|
165
|
+
query,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
type: queryMatch[1].toLowerCase(),
|
|
174
|
+
name: queryMatch[2] || operationName || 'default',
|
|
175
|
+
query,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Execute query operation
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
async _executeQueryOperation(operation, variables, context) {
|
|
184
|
+
const { name, query } = operation;
|
|
185
|
+
|
|
186
|
+
// Get resolver for this query
|
|
187
|
+
const resolver = this.resolvers.Query?.[name] || this.resolvers[name];
|
|
188
|
+
|
|
189
|
+
if (!resolver) {
|
|
190
|
+
throw new GraphQLError(`Resolver not found for query: ${name}`, 'RESOLVER_NOT_FOUND');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract field selections (simplified)
|
|
194
|
+
const fields = this._extractFields(query);
|
|
195
|
+
|
|
196
|
+
// Execute resolver
|
|
197
|
+
const result = await this._executeResolver(resolver, variables, context, fields);
|
|
198
|
+
|
|
199
|
+
return { [name]: result };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Execute mutation operation
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
async _executeMutationOperation(operation, variables, context) {
|
|
207
|
+
const { name, query } = operation;
|
|
208
|
+
|
|
209
|
+
// Get resolver for this mutation
|
|
210
|
+
const resolver = this.resolvers.Mutation?.[name] || this.resolvers[name];
|
|
211
|
+
|
|
212
|
+
if (!resolver) {
|
|
213
|
+
throw new GraphQLError(`Resolver not found for mutation: ${name}`, 'RESOLVER_NOT_FOUND');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Extract field selections (simplified)
|
|
217
|
+
const fields = this._extractFields(query);
|
|
218
|
+
|
|
219
|
+
// Execute resolver
|
|
220
|
+
const result = await this._executeResolver(resolver, variables, context, fields);
|
|
221
|
+
|
|
222
|
+
return { [name]: result };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Execute resolver function
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
async _executeResolver(resolver, variables, context, fields) {
|
|
230
|
+
if (typeof resolver === 'function') {
|
|
231
|
+
return await resolver(variables, context, fields);
|
|
232
|
+
} else if (typeof resolver === 'object' && resolver.resolve) {
|
|
233
|
+
return await resolver.resolve(variables, context, fields);
|
|
234
|
+
} else {
|
|
235
|
+
throw new GraphQLError('Invalid resolver', 'INVALID_RESOLVER');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Extract fields from GraphQL query (simplified)
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
_extractFields(query) {
|
|
244
|
+
// Simple field extraction (for lightweight implementation)
|
|
245
|
+
// In production, use proper GraphQL AST parser
|
|
246
|
+
const fieldMatch = query.match(/\{\s*([^}]+)\s*\}/);
|
|
247
|
+
if (fieldMatch) {
|
|
248
|
+
return fieldMatch[1]
|
|
249
|
+
.split(/\s+/)
|
|
250
|
+
.filter(f => f && !f.startsWith('('))
|
|
251
|
+
.map(f => f.replace(/[,\n\r]/g, '').trim())
|
|
252
|
+
.filter(f => f);
|
|
253
|
+
}
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Build context for GraphQL execution
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
async _buildContext(httpReq) {
|
|
262
|
+
const baseContext = {
|
|
263
|
+
req: httpReq,
|
|
264
|
+
headers: httpReq.headers || {},
|
|
265
|
+
query: httpReq.query || {},
|
|
266
|
+
params: httpReq.params || {},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (typeof this.context === 'function') {
|
|
270
|
+
const customContext = await this.context(httpReq);
|
|
271
|
+
return { ...baseContext, ...customContext };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return baseContext;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Send GraphQL response
|
|
279
|
+
* @private
|
|
280
|
+
*/
|
|
281
|
+
_sendResponse(res, result) {
|
|
282
|
+
res.statusCode = 200;
|
|
283
|
+
res.setHeader('Content-Type', 'application/json');
|
|
284
|
+
|
|
285
|
+
if (res.end) {
|
|
286
|
+
res.end(JSON.stringify(result));
|
|
287
|
+
} else {
|
|
288
|
+
res.body = result;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Send GraphQL error response
|
|
294
|
+
* @private
|
|
295
|
+
*/
|
|
296
|
+
_sendError(res, error) {
|
|
297
|
+
const formatted = this.formatError(error);
|
|
298
|
+
|
|
299
|
+
res.statusCode = error.code === 'BAD_REQUEST' ? 400 :
|
|
300
|
+
error.code === 'UNAUTHORIZED' ? 401 :
|
|
301
|
+
error.code === 'FORBIDDEN' ? 403 :
|
|
302
|
+
error.code === 'NOT_FOUND' ? 404 : 500;
|
|
303
|
+
res.setHeader('Content-Type', 'application/json');
|
|
304
|
+
|
|
305
|
+
const response = {
|
|
306
|
+
data: null,
|
|
307
|
+
errors: [formatted],
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
if (res.end) {
|
|
311
|
+
res.end(JSON.stringify(response));
|
|
312
|
+
} else {
|
|
313
|
+
res.body = response;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Default error formatter
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
321
|
+
_defaultFormatError(error) {
|
|
322
|
+
if (error instanceof GraphQLError) {
|
|
323
|
+
return {
|
|
324
|
+
message: error.message,
|
|
325
|
+
code: error.code,
|
|
326
|
+
extensions: error.extensions,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
message: error.message || 'Internal server error',
|
|
332
|
+
code: 'INTERNAL_ERROR',
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Create GraphQL server instance
|
|
339
|
+
* @param {Object} options - Server options
|
|
340
|
+
* @returns {GraphQLServer} - GraphQL server instance
|
|
341
|
+
*/
|
|
342
|
+
function createGraphQLServer(options = {}) {
|
|
343
|
+
return new GraphQLServer(options);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* GraphQL handler middleware factory
|
|
348
|
+
* @param {Object} options - Handler options
|
|
349
|
+
* @returns {Function} - Middleware function
|
|
350
|
+
*/
|
|
351
|
+
function graphql(options = {}) {
|
|
352
|
+
const server = new GraphQLServer(options);
|
|
353
|
+
return server.handler();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = {
|
|
357
|
+
GraphQLServer,
|
|
358
|
+
GraphQLError,
|
|
359
|
+
createGraphQLServer,
|
|
360
|
+
graphql,
|
|
361
|
+
};
|
|
362
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Resolver Utilities
|
|
3
|
+
* Helper functions for building GraphQL resolvers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a resolver function
|
|
8
|
+
*/
|
|
9
|
+
function createResolver(resolverFn, options = {}) {
|
|
10
|
+
const {
|
|
11
|
+
validate = null,
|
|
12
|
+
authorize = null,
|
|
13
|
+
cache = null,
|
|
14
|
+
errorHandler = null,
|
|
15
|
+
} = options;
|
|
16
|
+
|
|
17
|
+
return async (variables, context, fields) => {
|
|
18
|
+
try {
|
|
19
|
+
// Authorization check
|
|
20
|
+
if (authorize) {
|
|
21
|
+
const authorized = await authorize(context);
|
|
22
|
+
if (!authorized) {
|
|
23
|
+
throw new Error('Unauthorized');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validation
|
|
28
|
+
if (validate) {
|
|
29
|
+
const validationResult = await validate(variables);
|
|
30
|
+
if (!validationResult.valid) {
|
|
31
|
+
throw new Error(`Validation failed: ${validationResult.errors.join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cache check
|
|
36
|
+
if (cache && cache.get) {
|
|
37
|
+
const cacheKey = cache.key ? cache.key(variables, context) : null;
|
|
38
|
+
if (cacheKey) {
|
|
39
|
+
const cached = await cache.get(cacheKey);
|
|
40
|
+
if (cached !== null && cached !== undefined) {
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Execute resolver
|
|
47
|
+
const result = await resolverFn(variables, context, fields);
|
|
48
|
+
|
|
49
|
+
// Cache result
|
|
50
|
+
if (cache && cache.set && cache.key) {
|
|
51
|
+
const cacheKey = cache.key(variables, context);
|
|
52
|
+
await cache.set(cacheKey, result, cache.ttl || 3600);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (errorHandler) {
|
|
58
|
+
return await errorHandler(error, variables, context);
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a field resolver
|
|
67
|
+
*/
|
|
68
|
+
function fieldResolver(fieldName, resolverFn) {
|
|
69
|
+
return {
|
|
70
|
+
[fieldName]: resolverFn,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Combine multiple resolvers
|
|
76
|
+
*/
|
|
77
|
+
function combineResolvers(...resolvers) {
|
|
78
|
+
const combined = {
|
|
79
|
+
Query: {},
|
|
80
|
+
Mutation: {},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
resolvers.forEach(resolver => {
|
|
84
|
+
if (resolver.Query) {
|
|
85
|
+
Object.assign(combined.Query, resolver.Query);
|
|
86
|
+
}
|
|
87
|
+
if (resolver.Mutation) {
|
|
88
|
+
Object.assign(combined.Mutation, resolver.Mutation);
|
|
89
|
+
}
|
|
90
|
+
// Also support flat resolver structure
|
|
91
|
+
Object.keys(resolver).forEach(key => {
|
|
92
|
+
if (key !== 'Query' && key !== 'Mutation') {
|
|
93
|
+
combined.Query[key] = resolver[key];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return combined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create async resolver with retry logic
|
|
103
|
+
*/
|
|
104
|
+
function createAsyncResolver(resolverFn, options = {}) {
|
|
105
|
+
const {
|
|
106
|
+
maxRetries = 3,
|
|
107
|
+
retryDelay = 1000,
|
|
108
|
+
retryCondition = null,
|
|
109
|
+
} = options;
|
|
110
|
+
|
|
111
|
+
return async (variables, context, fields) => {
|
|
112
|
+
let lastError;
|
|
113
|
+
|
|
114
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
115
|
+
try {
|
|
116
|
+
return await resolverFn(variables, context, fields);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
lastError = error;
|
|
119
|
+
|
|
120
|
+
// Check if we should retry
|
|
121
|
+
if (attempt < maxRetries) {
|
|
122
|
+
if (retryCondition && !retryCondition(error)) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Wait before retry
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw lastError;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create batch resolver (for N+1 query problem)
|
|
138
|
+
*/
|
|
139
|
+
function createBatchResolver(resolverFn, options = {}) {
|
|
140
|
+
const {
|
|
141
|
+
batchKey = (variables) => JSON.stringify(variables),
|
|
142
|
+
batchSize = 100,
|
|
143
|
+
waitTime = 10,
|
|
144
|
+
} = options;
|
|
145
|
+
|
|
146
|
+
let batch = [];
|
|
147
|
+
let batchTimer = null;
|
|
148
|
+
|
|
149
|
+
const processBatch = async () => {
|
|
150
|
+
if (batch.length === 0) return;
|
|
151
|
+
|
|
152
|
+
const currentBatch = batch.splice(0, batchSize);
|
|
153
|
+
batchTimer = null;
|
|
154
|
+
|
|
155
|
+
// Group by batch key
|
|
156
|
+
const groups = {};
|
|
157
|
+
currentBatch.forEach(({ variables, context, fields, resolve, reject }) => {
|
|
158
|
+
const key = batchKey(variables);
|
|
159
|
+
if (!groups[key]) {
|
|
160
|
+
groups[key] = [];
|
|
161
|
+
}
|
|
162
|
+
groups[key].push({ variables, context, fields, resolve, reject });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Execute each group
|
|
166
|
+
Object.keys(groups).forEach(async key => {
|
|
167
|
+
const group = groups[key];
|
|
168
|
+
try {
|
|
169
|
+
const result = await resolverFn(group[0].variables, group[0].context, group[0].fields);
|
|
170
|
+
group.forEach(({ resolve }) => resolve(result));
|
|
171
|
+
} catch (error) {
|
|
172
|
+
group.forEach(({ reject }) => reject(error));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return async (variables, context, fields) => {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
batch.push({ variables, context, fields, resolve, reject });
|
|
180
|
+
|
|
181
|
+
if (!batchTimer) {
|
|
182
|
+
batchTimer = setTimeout(processBatch, waitTime);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (batch.length >= batchSize) {
|
|
186
|
+
clearTimeout(batchTimer);
|
|
187
|
+
processBatch();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
createResolver,
|
|
195
|
+
fieldResolver,
|
|
196
|
+
combineResolvers,
|
|
197
|
+
createAsyncResolver,
|
|
198
|
+
createBatchResolver,
|
|
199
|
+
};
|
|
200
|
+
|