mirror of
https://github.com/Ratstail91/sineQL.git
synced 2025-11-29 02:34:28 +11:00
It seems to be working in a mock-environment
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
const buildTypeGraph = require('./build-type-graph');
|
const buildTypeGraph = require('./build-type-graph');
|
||||||
const parseInput = require('./parse-input');
|
const parseInput = require('./parse-input');
|
||||||
|
const parseQueryTree = require('./parse-query-tree');
|
||||||
|
|
||||||
//the main function to be returned (sineQL())
|
//the main function to be returned (sineQL())
|
||||||
const sineQL = (schema, handler, options = {}) => {
|
const sineQL = (schema, { queryHandlers }, options = {}) => {
|
||||||
let typeGraph;
|
let typeGraph;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -18,21 +19,27 @@ const sineQL = (schema, handler, options = {}) => {
|
|||||||
try {
|
try {
|
||||||
//parse the query
|
//parse the query
|
||||||
const tokens = parseInput(reqBody, true, options);
|
const tokens = parseInput(reqBody, true, options);
|
||||||
let pos = 0;
|
const queryTree = parseQueryTree(tokens, typeGraph, options);
|
||||||
|
|
||||||
switch(tokens[pos]) {
|
switch(tokens[0]) {
|
||||||
//check for keywords
|
//check for leading keywords
|
||||||
case 'create':
|
case 'create':
|
||||||
case 'update':
|
case 'update':
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return [501, 'Keyword not implemented: ' + tokens[pos]];
|
return [501, 'Keyword not implemented: ' + tokens[0]];
|
||||||
//TODO: implement these keywords
|
//TODO: implement these keywords
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//no leading keyword - regular query
|
//no leading keyword - regular query
|
||||||
default:
|
default: {
|
||||||
//TODO: implement queries
|
const result = await queryHandlers[queryTree.typeName](queryTree, typeGraph);
|
||||||
return [501, 'Queries not implemented'];
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log('Query tree results:\n', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [200, result];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
|
|||||||
98
source/parse-query-tree.js
Normal file
98
source/parse-query-tree.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
//build the tokens into a single object of types representing the initial query
|
||||||
|
const parseQueryTree = (tokens, typeGraph, options) => {
|
||||||
|
let current = 1; //primed
|
||||||
|
|
||||||
|
//TODO: check for top-level keywords
|
||||||
|
|
||||||
|
//get a token that matches a type
|
||||||
|
if (!typeGraph[tokens[current - 1]]) {
|
||||||
|
throw `Expected a type in the type graph (found ${tokens[current - 1]})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//read the block of lines
|
||||||
|
const [block, pos] = readBlock(tokens, current, tokens[current - 1], typeGraph, options);
|
||||||
|
|
||||||
|
//insert the typename into the top-level block
|
||||||
|
block['typeName'] = tokens[current - 1];
|
||||||
|
|
||||||
|
//finally
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readBlock = (tokens, current, superType, typeGraph, options) => {
|
||||||
|
//scan the '{'
|
||||||
|
if (tokens[current++] != '{') {
|
||||||
|
throw `Expected '{'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//result
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
//scan each "line" in this block
|
||||||
|
while(tokens[current++]) {
|
||||||
|
//check for end of block
|
||||||
|
if (tokens[current - 1] == '}') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//check for block-level keywords
|
||||||
|
let modifier = null;
|
||||||
|
if (['match'].includes(tokens[current - 1])) {
|
||||||
|
modifier = tokens[current - 1];
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//read field name
|
||||||
|
const fieldName = tokens[current - 1];
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log(`Trying to process field ${fieldName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if the field is non-scalar, read the sub-block
|
||||||
|
if (!typeGraph[typeGraph[superType][fieldName].typeName].scalar) {
|
||||||
|
//recurse
|
||||||
|
const [block, pos] = readBlock(tokens, current, typeGraph[superType][fieldName].typeName, typeGraph, options);
|
||||||
|
|
||||||
|
//insert the typename into the block
|
||||||
|
block['typeName'] = typeGraph[superType][fieldName].typeName;
|
||||||
|
|
||||||
|
//insert into result
|
||||||
|
result[fieldName] = block;
|
||||||
|
|
||||||
|
//insert the block-level modifier signal
|
||||||
|
if (modifier) {
|
||||||
|
result[fieldName][modifier] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = pos; //pos points past the end of the block
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log(`${fieldName}: ${JSON.stringify(result[fieldName])}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//scalar
|
||||||
|
else {
|
||||||
|
//save the typeGraph type into result
|
||||||
|
result[fieldName] = JSON.parse(JSON.stringify( typeGraph[ typeGraph[superType][fieldName].typeName ] ));
|
||||||
|
|
||||||
|
//insert the block-level modifier signal
|
||||||
|
if (modifier) {
|
||||||
|
result[fieldName][modifier] = tokens[current++];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log(`${fieldName}: `, result[fieldName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [result, current];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = parseQueryTree;
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
//mock tools
|
//mock tools
|
||||||
|
const Op = {
|
||||||
|
eq: 'eq'
|
||||||
|
};
|
||||||
|
|
||||||
const books = {
|
const books = {
|
||||||
findAll: async args => {
|
findAll: async args => {
|
||||||
let arr = [
|
let arr = [
|
||||||
{ title: 'The Wind in the Willows', published: '1908-06-15' }
|
{ id: 1, title: 'The Wind in the Willows', published: '1908-06-15' },
|
||||||
|
{ id: 2, title: 'The Fart in the Fronds', published: '1908-06-15' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const { attributes, where } = args;
|
const { attributes, where } = args;
|
||||||
|
|
||||||
arr = arr.filter(el => !where || el.title == where.title || el.published == where.published); //TODO: fix this
|
//TODO: make this more generic
|
||||||
|
console.log('books attributes:', attributes);
|
||||||
|
console.log('books where', where);
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
@@ -15,13 +22,16 @@ const books = {
|
|||||||
|
|
||||||
const authors = {
|
const authors = {
|
||||||
findAll: async args => {
|
findAll: async args => {
|
||||||
const arr = [
|
let arr = [
|
||||||
{ name: 'Kenneth Grahame', bookIds: [1] },
|
{ name: 'Kenneth Grahame', books: [1] },
|
||||||
|
{ name: 'Frank', books: [1, 2] },
|
||||||
|
{ name: 'Betty', books: [2] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const { attributes, where } = args;
|
const { attributes, where } = args;
|
||||||
|
|
||||||
arr = arr.filter(el => !where || el.title == where.title || el.published == where.published); //TODO: fix this
|
console.log('authors attributes:', attributes);
|
||||||
|
console.log('authors where', where);
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
@@ -51,59 +61,71 @@ const authors = {
|
|||||||
//depth-first search seems to be the best option
|
//depth-first search seems to be the best option
|
||||||
//Each query shouldn't know if it's a sub-query
|
//Each query shouldn't know if it's a sub-query
|
||||||
|
|
||||||
const handler = {
|
const queryHandlers = {
|
||||||
//complex compound
|
//complex compound
|
||||||
Author: async (query, graph) => {
|
Author: async (query, graph) => {
|
||||||
|
//DEBUGGING
|
||||||
|
// console.log('Author():', query);
|
||||||
|
|
||||||
//get the fields alone
|
//get the fields alone
|
||||||
const { typeName, ...fields } = query;
|
const { typeName, ...fields } = query;
|
||||||
|
|
||||||
//get the names of matched fields
|
//get the names of matched fields
|
||||||
const matchedNames = Object.keys(fields.filter(field => field.match));
|
const matchedNames = Object.keys(fields).filter(field => fields[field].match);
|
||||||
|
|
||||||
//short-circuit if querying everything
|
//short-circuit if querying everything
|
||||||
const where = {};
|
const where = {};
|
||||||
if (matchedNames.length > 0) {
|
if (matchedNames.length > 0) {
|
||||||
//build the "where" object
|
//build the "where" object
|
||||||
matchedNames.forEach(mn => {
|
matchedNames.forEach(mn => {
|
||||||
where[mn] = {
|
if (query[mn].match !== true) {
|
||||||
[Op.eq]: query[mn].match
|
where[mn] = { [Op.eq]: query[mn].match };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//these are field names
|
//these are field names
|
||||||
const scalars = Object.keys(fields).filter(field => graph[field.typeName].scalar);
|
const scalars = Object.keys(fields).filter(field => graph[fields[field].typeName].scalar);
|
||||||
const nonScalars = Object.keys(fields).filter(field => !graph[field.typeName].scalar);
|
const nonScalars = Object.keys(fields).filter(field => !graph[fields[field].typeName].scalar);
|
||||||
|
|
||||||
const results = await authors.findAll({
|
const authorResults = await authors.findAll({
|
||||||
attributes: scalars, //fields to find (keys)
|
attributes: scalars, //fields to find (keys)
|
||||||
where: where
|
where: where
|
||||||
}); //sequelize ORM model
|
}); //sequelize ORM model
|
||||||
|
|
||||||
nonScalars.forEach(nonScalar => {
|
const promiseArray = nonScalars.map(async nonScalar => {
|
||||||
//delegate to a deeper part of the tree
|
//delegate to a deeper part of the tree
|
||||||
results[nonScalar] = handler[fields[nonScalar].typeName](fields[nonScalar], graph);
|
const nonScalarArray = await queryHandlers[fields[nonScalar].typeName](fields[nonScalar], graph);
|
||||||
|
|
||||||
|
//for each author, update this non-scalar field with the non-scalar's recursed value
|
||||||
|
authorResults.forEach(author => {
|
||||||
|
author[nonScalar] = nonScalarArray.filter(ns => author[nonScalar].includes(ns.id));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Promise.all(promiseArray);
|
||||||
|
|
||||||
//finally, return the results
|
//finally, return the results
|
||||||
return results;
|
return authorResults;
|
||||||
},
|
},
|
||||||
|
|
||||||
//simple compound
|
//simple compound
|
||||||
Book: async (query, graph) => {
|
Book: async (query, graph) => {
|
||||||
|
// console.log('Book():', query);
|
||||||
|
|
||||||
//get the fields alone
|
//get the fields alone
|
||||||
const { typeName, ...fields } = query;
|
const { typeName, ...fields } = query;
|
||||||
|
|
||||||
//get the names of matched fields
|
//get the names of matched fields
|
||||||
const matchedNames = Object.keys(fields.filter(field => field.match));
|
const matchedNames = Object.keys(fields).filter(field => fields[field].match);
|
||||||
|
|
||||||
//short-circuit if querying everything
|
//short-circuit if querying everything
|
||||||
const where = {};
|
const where = {};
|
||||||
if (matchedNames.length > 0) {
|
if (matchedNames.length > 0) {
|
||||||
//build the "where" object
|
//build the "where" object
|
||||||
matchedNames.forEach(mn => {
|
matchedNames.forEach(mn => {
|
||||||
where[mn] = {
|
if (query[mn].match !== true) {
|
||||||
[Op.eq]: query[mn].match
|
where[mn] = { [Op.eq]: query[mn].match };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -135,4 +157,10 @@ type Author {
|
|||||||
const sineQL = require('../source/index.js');
|
const sineQL = require('../source/index.js');
|
||||||
|
|
||||||
//run the function in debug mode (builds type graph)
|
//run the function in debug mode (builds type graph)
|
||||||
const sine = sineQL(schema, handler, { debug: true });
|
const sine = sineQL(schema, { queryHandlers }, { debug: true });
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const [code, result] = await sine('Author { name match books { match title "The Wind in the Willows" published } }');
|
||||||
|
|
||||||
|
console.log('\n\n', JSON.stringify(result));
|
||||||
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user