Skip to content

Commit 307ed95

Browse files
authored
Make serverjs a module (#118)
* add placholder schemas and resolvers to graphql-server/resources dir * add app.js as entrypoint, make server into a module, update ArangoDB driver init * add package.json to graphql-server root * substitute driver's package.json with setup script and update build script * disable optional fields in client-tests.js * Export the server as a module to increase reusability and simplify testing (#117) * Pass the resolvers to the server as objects * simplify client-tests.js to run for more complex schemas * remove separate driver dependency setup * use explicit variable declaration * use inline defaults for custom schema and resolvers * add automated test for some basic server and API functionality (see issue #94) * basic tests for get by ID, list of, and reverse edges * fix var name for database URL, and update var env var name from URL to DB_URL * add missing interface related resolvers (issue #122) * fix query over possible types in totalCount and isEndOfList * add more fine grained tests for GraphQL server * resolves issue #123 * minor edit before implementing union tests * add tests for types with union edge * update list of tests * bugfix: support for adding annotations when creating edges * add support for querying union by ID * add tests for edge operations * minor update * update test schema * support exported variables for updaing edge * add test for exporting variable * updated README * add schema for directives tests * add test for deleting edge object when deleting one of its vertices * add schema and resolvers for directives tests * add tests for directives * remove info log to increase readability * update readme * clean up old test files * use port 4001 for tests
1 parent ae4e1df commit 307ed95

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+7642
-4113
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
**v0.1.5, to be released on ???**
2+
* New feature: Additional tests for API and driver functionality
3+
* Bug fix: Fix listOfX crashing for interfaces. See: https://github.com/LiUGraphQL/woo.sh/issues/122
24
* Bug fix: Fix failing validation of union for edge. See: https://github.com/LiUGraphQL/woo.sh/issues/123
5+
* Refactor: Export the server as a module to increase reusability and simplify testing. See: https://github.com/LiUGraphQL/woo.sh/issues/117
36

47
**v0.1.4, released on September 21, 2020**
58
* New feature: Add tests for API and driver functionality

graphql-resolver-generator/resources/resolver.template

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ const resolvers = {
3535

3636
% for interface_name in data['interfaces']:
3737
% if interface_name[0] != '_':
38-
listOf${interface_name}s: async (parent, args, context, info) => await driver.getList(args, info),
38+
${interface_name[0].lower() + interface_name[1:]}: async (parent, args, context, info) =>
39+
await driver.get(args.id, info.returnType, info.schema),
40+
% endif
41+
% endfor
42+
43+
% for interface_name in data['interfaces']:
44+
% if interface_name[0] != '_':
45+
listOf${interface_name}s: async (parent, args, context, info) =>
46+
await driver.getList(args, info),
3947
% endif
4048
% endfor
4149
},
@@ -134,6 +142,17 @@ const resolvers = {
134142
% endif
135143
% endfor
136144

145+
% for interface_name in data['interfaces']:
146+
% if interface_name[0] != '_':
147+
_ListOf${interface_name}s: {
148+
totalCount: async (parent, args, context, info) =>
149+
await driver.getTotalCount(parent, args, info),
150+
isEndOfWholeList: async (parent, args, context, info) =>
151+
await driver.isEndOfList(parent, args, info),
152+
},
153+
% endif
154+
% endfor
155+
137156
% for type in data['types']:
138157
% for field, field_type in type['edgeFieldEndpoints']:
139158
_${field}EdgeFrom${type['Name']}: {
@@ -146,6 +165,15 @@ const resolvers = {
146165
% endfor
147166
% endfor
148167

168+
% for interface_name in data['interfaces']:
169+
% if interface_name.startswith('_'):
170+
${interface_name}: {
171+
__resolveType: (parent, args, context, info) =>
172+
parent.__typename
173+
},
174+
% endif
175+
% endfor
176+
149177
% for interface_name in data['interfaces']:
150178
% if interface_name.startswith('_'):
151179
${interface_name}: {

graphql-server/app.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const { makeServer } = require('./server');
2+
const { readFileSync } = require('fs');
3+
require('dotenv').config();
4+
5+
const baseSchema = readFileSync(process.env.API_SCHEMA || './resources/api-schema.graphql', 'utf8');
6+
const customSchema = readFileSync(process.env.CUSTOM_API_SCHEMA || './resources/custom-api-schema.graphql', 'utf8');
7+
const resolvers = require(process.env.RESOLVERS || './resources/resolvers.js', 'utf8');
8+
const customResolvers = require(process.env.CUSTOM_RESOLVERS || './resources/custom-resolvers.js', 'utf8');
9+
10+
let options = {
11+
baseSchema,
12+
customSchema,
13+
resolvers,
14+
customResolvers,
15+
'driver': process.env.DRIVER || 'arangodb',
16+
'dbName': process.env.DB_NAME || 'dev-db',
17+
'dbUrl': process.env.DB_URL || 'http://localhost:8529',
18+
'drop': process.env.DROP === 'true',
19+
'disableDirectivesChecking': process.env.DISABLE_DIRECTIVES_CHECKING === 'true',
20+
'disableEdgeValidation': process.env.DISABLE_EDGE_VALIDATION === 'true',
21+
'debug': process.env.DEBUG === 'true'
22+
};
23+
24+
makeServer(options).then( server => {
25+
server.listen().then(({ url }) => {
26+
console.log('\x1b[33m%s\x1b[0m', `GraphQL server is running at ${url}`);
27+
});
28+
});

graphql-server/drivers/arangodb/driver.js

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const graphql = require('graphql');
22
const arangojs = require("arangojs");
33
const aql = arangojs.aql;
44
const { makeExecutableSchema } = require('graphql-tools');
5-
const { ApolloError } = require('apollo-server');
5+
const { ApolloError, gql } = require('apollo-server');
66
const waitOn = require('wait-on');
77

88
let db;
@@ -62,9 +62,9 @@ module.exports = {
6262
};
6363

6464
async function init(args) {
65-
let typeDefs = args.typeDefs;
65+
let typeDefs = gql`${args.baseSchema}`;
6666
let dbName = args.dbName || 'dev-db';
67-
let url = args.url || 'http://localhost:8529';
67+
let url = args.dbUrl || 'http://localhost:8529';
6868
let drop = args.drop || false;
6969
disableDirectivesChecking = args['disableDirectivesChecking'] || false;
7070
disableEdgeValidation = args['disableEdgeValidation'] || false;
@@ -83,7 +83,7 @@ async function init(args) {
8383
console.info(`ArangoDB is now available at ${url}`);
8484

8585
// if drop is set
86-
dblist = await db.listDatabases();
86+
let dblist = await db.listDatabases();
8787
if (drop && dblist.includes(dbName)) {
8888
await db.dropDatabase(dbName).then(
8989
() => console.info(`Database ${dbName} dropped.`),
@@ -324,11 +324,7 @@ function createEdge(isRoot, ctxt, varOrSourceID, sourceType, sourceField, varOrT
324324
// prepare annotations
325325
if (annotations === null || annotations === undefined) annotations = {};
326326
// substitute fields defined by exported variables
327-
annotations = substituteExportedVariables(annotations, ctxt);
328-
let annotationType = info.schema.getType(`_InputToAnnotate${collectionName}`);
329-
if (annotationType) { // RK: Should we be able to skip this due to previous validation?
330-
annotations = getScalarsAndEnums(annotations, info.schema.getType(annotationType));
331-
}
327+
const substitutedFields = substituteExportedVariables(annotations, ctxt);
332328

333329
// create a new variable if resVar was not defined by the calling function
334330
resVar = resVar !== null ? resVar : createVar(ctxt);
@@ -355,7 +351,7 @@ function createEdge(isRoot, ctxt, varOrSourceID, sourceType, sourceField, varOrT
355351
validateEdge(ctxt, source, sourceType, sourceField, target, targetType, info, validateSource, validateTarget);
356352

357353
// insert edge
358-
ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT MERGE(${asAQLVar(docVar)}, {'_from': ${source}, '_to': ${target} }) IN ${asAQLVar(collectionVar)} RETURN NEW\`).next();`);
354+
ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT MERGE(${asAQLVar(docVar)}, ${stringifyImportedFields(substitutedFields)}, {'_from': ${source}, '_to': ${target} }) IN ${asAQLVar(collectionVar)} RETURN NEW\`).next();`);
359355

360356
// add exported variables from selection fields for root only
361357
addExportedVariables(isRoot, resVar, info, ctxt);
@@ -475,13 +471,16 @@ function updateEdge(isRoot, ctxt, id, data, edgeName, inputToUpdateType, info, r
475471
const collectionVar = getCollectionVar(edgeName, ctxt, true);
476472
ctxt.trans.code.push(`\n\t/* update edge ${edgeName} */`);
477473

474+
// substitute fields defined by exported variables
475+
const substitutedFields = substituteExportedVariables(data, ctxt);
476+
478477
// define doc
479478
const doc = getScalarsAndEnums(data, info.schema.getType(inputToUpdateType));;
480479
doc['_lastUpdateDate'] = new Date().valueOf();
481480
const docVar = addParameterVar(ctxt, createParamVar(ctxt), doc);
482481
const idVar = addParameterVar(ctxt, createParamVar(ctxt), id);
483482

484-
ctxt.trans.code.push(`let ${resVar} = db._query(aql\`UPDATE PARSE_IDENTIFIER(${asAQLVar(idVar)}).key WITH ${asAQLVar(docVar)} IN ${asAQLVar(collectionVar)} RETURN NEW\`).next();`);
483+
ctxt.trans.code.push(`let ${resVar} = db._query(aql\`UPDATE PARSE_IDENTIFIER(${asAQLVar(idVar)}).key WITH MERGE(${asAQLVar(docVar)}, ${stringifyImportedFields(substitutedFields)}) IN ${asAQLVar(collectionVar)} RETURN NEW\`).next();`);
485484

486485
//directives handling is not needed for edge updates as they can not have directives as of current
487486

@@ -581,7 +580,7 @@ function exportSelection(isRoot, resVar, selection, info, ctxt){
581580
*/
582581
function substituteExportedVariables(data, ctxt){
583582
const substitutes = {};
584-
Object.entries(data).forEach((entry) => {
583+
for(let entry of Object.entries(data)) {
585584
const fieldName = entry[0];
586585
const value = entry[1];
587586
// if value is an object it must be a variable
@@ -597,7 +596,7 @@ function substituteExportedVariables(data, ctxt){
597596
}
598597
}
599598
}
600-
});
599+
}
601600
return substitutes;
602601
}
603602

@@ -827,7 +826,7 @@ function deleteObject(isRoot, ctxt, varOrID, typeToDelete, info, resVar = null)
827826
async function get(id, returnType, schema) {
828827
let type = returnType;
829828
let query = [aql`FOR i IN`];
830-
if (graphql.isInterfaceType(type)) {
829+
if (graphql.isInterfaceType(type) || graphql.isUnionType(type)) {
831830
let possible_types = schema.getPossibleTypes(type);
832831
if (possible_types.length > 1) {
833832
query.push(aql`UNION(`);
@@ -866,7 +865,7 @@ async function get(id, returnType, schema) {
866865
}
867866

868867
/**
869-
* Get the source/target an given edge field connected to parent.
868+
* Get the source/target of a given edge field connected to parent.
870869
*
871870
* @param parent
872871
* @param args
@@ -1058,7 +1057,7 @@ async function getList(args, info) {
10581057
const cursor = await db.query(q);
10591058
let result = await cursor.all();
10601059
let list = {
1061-
'_filter': queryFilters, // needed to resolve fields 'isEndOfList' and 'totalLength'
1060+
'_filter': queryFilters, // needed to resolve fields 'isEndOfList' and 'totalCount'
10621061
'content': result
10631062
};
10641063
return list;
@@ -1531,13 +1530,12 @@ function getFilters(filterArg, type_to_filter, alias = 'x') {
15311530
async function isEndOfList(parent, args, info) {
15321531
let type = graphql.getNamedType(info.parentType.getFields()['content'].type);
15331532
let query = [aql`FOR x IN FLATTEN(FOR i IN [`];
1534-
addPossibleTypes(query, info.schema, type);
1533+
query.push(getPossibleTypes(type, info.schema));;
15351534
query.push(aql`] RETURN i)`);
15361535

15371536
// add filters
1538-
if (parent._filter) {
1539-
query = query.concat(parent._filter);
1540-
}
1537+
query.push(...parent._filter);
1538+
15411539
// get last ID in parent content
15421540
if (parent.content.length != 0) {
15431541
const last = parent.content[parent.content.length - 1];
@@ -1546,6 +1544,7 @@ async function isEndOfList(parent, args, info) {
15461544

15471545
query.push(aql`SORT x._id COLLECT WITH COUNT INTO length RETURN length`);
15481546
try {
1547+
console.debug(aql.join(query));
15491548
const cursor = await db.query(aql.join(query));
15501549
const result = await cursor.next();
15511550
return result == 0;
@@ -1567,16 +1566,15 @@ async function isEndOfList(parent, args, info) {
15671566
async function getTotalCount(parent, args, info) {
15681567
let type = graphql.getNamedType(info.parentType.getFields()['content'].type);
15691568
let query = [aql`FOR x IN FLATTEN(FOR i IN [`];
1570-
addPossibleTypes(query, info.schema, type);
1569+
query.push(getPossibleTypes(type, info.schema));
15711570
query.push(aql`] RETURN i)`);
15721571

15731572
// add filters
1574-
if (parent._filter) {
1575-
query = query.concat(parent._filter);
1576-
}
1573+
query.push(...parent._filter);
15771574

15781575
query.push(aql`COLLECT WITH COUNT INTO length RETURN length`);
15791576
try {
1577+
console.debug(aql.join(query));
15801578
const cursor = await db.query(aql.join(query));
15811579
return await cursor.next();
15821580
} catch (err) {
@@ -1621,6 +1619,31 @@ function createParamVar(ctxt) {
16211619
return `_${ctxt.paramVarCounter}`;
16221620
}
16231621

1622+
/**
1623+
* Return an AQL query fragment listing all possible collections for the given type.
1624+
*
1625+
* If context is not null, collections are explicitly bound to AQL variables.
1626+
*
1627+
* @param type
1628+
* @param schema
1629+
* @param conext = null (optional)
1630+
*/
1631+
function getPossibleTypes(type, schema, ctxt = null) {
1632+
let collections = [];
1633+
let types = graphql.isUnionType(type) || graphql.isInterfaceType(type) ? schema.getPossibleTypes(type): [type];
1634+
1635+
for (let t of types) {
1636+
if (ctxt !== null) {
1637+
let collectionVar = asAQLVar(getCollectionVar(t.name, ctxt, true));
1638+
collections.push(collectionVar);
1639+
} else {
1640+
let collection = db.collection(t.name)
1641+
collections.push(aql`${collection}`);
1642+
}
1643+
}
1644+
return aql.join(collections, ',');
1645+
}
1646+
16241647
/**
16251648
* Add all possible collections for the given type to query
16261649
* If context is not null, it is assumed it is for a mutation.

graphql-server/drivers/arangodb/package.json

Lines changed: 0 additions & 24 deletions
This file was deleted.

graphql-server/empty_custom_api_schema.graphql

Whitespace-only changes.

graphql-server/empty_custom_resolvers.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)