-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgraph.js
181 lines (165 loc) · 4.61 KB
/
graph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
'use strict';
var Q = require('q');
var constants = require('./constants');
function qryqGraph(context) {
if (typeof context !== 'object') {
throw new Error('Expected context');
}
if (typeof context.api !== 'object') {
throw new Error('Expected an API object');
}
var fluent = {
queries: [],
query: query,
api: api,
input: input,
depends: depends,
filterOutput: filterOutput,
allQueries: allQueries,
run: run,
};
var currentQuery;
function _saveCurrentQuery() {
if (!!currentQuery) {
validateQuery(currentQuery);
fluent.queries.push(currentQuery);
}
}
/**
* query begins describing a new query.
* subsequent calls to input, depends, and filterOutput will apply to this query
*
* @param id {string}
* @return {Fluent}
*/
function query(id) {
_saveCurrentQuery();
currentQuery = { id: id };
return fluent;
}
/**
* api specifies the name of the API the current query should invoke
*
* @param name {string}
* @return {Fluent}
*/
function api(name) {
currentQuery.api = name;
return fluent;
}
/**
* input specifies what the input to pass to the identified API function.
*
* Input fields may be specified as strings such as `'#{ANOTHER_QUERYS_NAME}'`
* or `'#{ANOTHER_QUERYS_NAME}.foo.bar'`.
* qryq will then identify these other queries as the current query's dependents,
* and wait for them to complete execution before beginning this one.
* It will also substitute the correct values from the return value of the
* dependent queries.
*
* @param data {Object}
* @return {Fluent}
*/
function input(data) {
currentQuery.input = data;
return fluent;
}
/**
* depends specifies which other queries need to complete
* execution prior to beginning executing the current one.
*
* This is **optional**, as if this is not specified,
* qryq will parse the input object for expressions.
*
* @param dependIds {Array<string>}
* @return {Fluent}
*/
function depends(dependIds) {
currentQuery.depends = dependIds;
return fluent;
}
/**
* filterOutput, when called with `true`, specifies that the output of the
* current query should be **excluded** from the final result returned.
*
* This is **optional**, as is this is not specified,
* qryq simply inlcudes the results in the output by default.
*
* @param shouldFilter {boolean}
* @return {Fluent}
*/
function filterOutput(shouldFilter) {
currentQuery.filterOutput = !!shouldFilter;
return fluent;
}
/**
* allQueries specifies the entire list of queries.
*
* This should only be called once,
* and other queries should not be manually specified if this is used.
*
* @param {Array<GraphQuery>}
* @return {Fluent}
*/
function allQueries(list) {
fluent.queries = [];
list.forEach(function(query) {
currentQuery = query;
_saveCurrentQuery();
currentQuery = undefined;
});
return fluent;
}
/**
* run finalises the list of queries and then executes them.
* The promise it returns will resolve the final results of the
* dependent set of queries.
*
* @return {Promise}
*/
function run() {
_saveCurrentQuery();
var deferred = Q.defer();
//TODO validate graph - ensure that all depends exist, and that there are no cycles
//TODO execute graph of queries using deferred and fluent.queries
require('./graph-run')(deferred, context.api, fluent.queries);
return deferred.promise;
}
return fluent;
}
module.exports = qryqGraph;
function inferDependsFromQueryInputs(query) {
if (!!query.depends) {
// Skip inferring depends if it already has been set
return;
}
query.depends = [];
var inputAsString = JSON.stringify(query.input);
var found = inputAsString.match(constants.GRAPH_DEPENDENT_INFER_REGEX);
if (found && found.length > 0) {
for (var idx = 0; idx < found.length; ++idx) {
var tok = found[idx];
var match = tok.match(constants.GRAPH_DEPENDENT_SUBSTITUTE_REGEX);
if (match && match.length > 1) {
var tmp = match[1];
var subKeys = tmp.split('.');
query.depends.push(subKeys[0]);
}
}
}
}
function validateQuery(query) {
if (typeof query !== 'object') {
throw new Error('Query is not defined');
}
if (typeof query.id !== 'string') {
throw new Error('Query needs an ID');
}
if (typeof query.api !== 'string') {
throw new Error('Query needs an API ID');
}
if (typeof query.input !== 'object') {
throw new Error('Query needs an input object');
}
inferDependsFromQueryInputs(query);
}