Refactor dlayer

feature/node-rewrite
Sven Slootweg 2 years ago
parent 1869fc3792
commit edc6e0f8be

@ -3,16 +3,35 @@
// Simple data type to represent a query path and corresponding schema path tied together, because these are basically always used together, and it would bloat up the implementation code otherwise
function createInstance({ queryPath, schemaPath, queryObject, schemaObject }) {
console.log("create", { queryPath, schemaPath, queryObject, schemaObject });
return {
query: queryPath,
schema: schemaPath,
child: function (property, { queryOverride, schemaOverride } = {}) {
queryPath: queryPath,
schemaPath: schemaPath,
query: queryObject,
schema: schemaObject,
child: function (queryKey, schemaKey, { queryOverride, schemaOverride } = {}) {
return createInstance({
queryPath: queryPath.concat([ property ]),
schemaPath: schemaPath.concat([ property ]),
queryObject: queryOverride ?? queryObject[property],
schemaObject: schemaOverride ?? schemaObject[property]
queryPath: (queryKey != null)
? queryPath.concat([ queryKey ])
: queryPath,
schemaPath: (schemaKey != null)
? schemaPath.concat([ schemaKey ])
: schemaPath,
queryObject: queryOverride ?? queryObject[queryKey],
schemaObject: schemaOverride ?? schemaObject[schemaKey]
});
},
toPathString: function () {
return queryPath
.map((segment, i) => {
if (segment === schemaPath[i]) {
return segment;
} else {
// This is used for representing aliases, showing the original schema key in brackets
return `${segment} [${schemaPath[i]}]`;
}
})
.join(" -> ");
}
};
}

@ -4,6 +4,7 @@ const Promise = require("bluebird");
const mapObject = require("map-obj");
const Result = require("../result");
const createCursor = require("./cursor");
// TODO: Bounded/unbounded recursion
// TODO: context
@ -20,19 +21,6 @@ The schema merging will eventually become deep-merging, when multi-level recursi
const specialKeyRegex = /^\$[^\$]/;
function stringifyPath(queryPath, schemaPath) {
return queryPath
.map((segment, i) => {
if (segment === schemaPath[i]) {
return segment;
} else {
// This is used for representing aliases, showing the original schema key in brackets
return `${segment} [${schemaPath[i]}]`;
}
})
.join(" -> ");
}
function maybeCall(value, args, thisContext) {
return Promise.try(() => {
// FIXME: Only do this for actual fetch requests
@ -92,44 +80,42 @@ function analyzeSubquery(subquery) {
return { isRecursive, allowErrors, hasChildKeys, isLeaf, args };
}
function analyzeQueryKey(schemaObject, queryObject, queryKey) {
let subquery = queryObject[queryKey];
let schemaKey = subquery?.$key ?? queryKey; // $key is for handling aliases
let handler = schemaObject[schemaKey] ?? schemaObject.$anyKey;
function analyzeQueryKey(cursor, queryKey) {
let childCursor = cursor.child(queryKey, null);
let schemaKey = childCursor.query?.$key ?? queryKey; // $key is for handling aliases
let handler = cursor.child(queryKey, schemaKey).schema ?? cursor.schema.$anyKey;
// TODO: Maybe clean up all the .child stuff here by moving the `$key` logic into the cursor implementation instead, as it seems like nothing else in dlayer needs to care about the aliasing
return {
... analyzeSubquery(subquery),
... analyzeSubquery(childCursor.query),
schemaKey: schemaKey,
handler: handler
};
}
function assignErrorPath(error, queryPath, schemaPath) {
function assignErrorPath(error, cursor) {
if (error.path == null) {
// Only assign the path if it hasn't already happened at a deeper level; this is a recursive function after all
error.path = queryPath;
error.message = error.message + ` (${stringifyPath(queryPath, schemaPath)})`;
error.path = cursor.queryPath;
error.message = error.message + ` (${cursor.toPathString()})`;
}
}
function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
function evaluate(cursor, context) {
// map query object -> result object
return asyncMapObject(queryObject, (queryKey, subquery) => {
return asyncMapObject(cursor.query, (queryKey, subquery) => {
let shouldFetch = (subquery !== false);
if (!shouldFetch || specialKeyRegex.test(queryKey)) {
// When constructing the result object, we only care about the 'real' keys, not about special meta-keys like $key; those get processed in the actual resolution logic itself.
return mapObject.mapObjectSkip;
} else {
let { schemaKey, handler, args, isRecursive, allowErrors, isLeaf } = analyzeQueryKey(schemaObject, queryObject, queryKey);
let { schemaKey, handler, args, isRecursive, allowErrors, isLeaf } = analyzeQueryKey(cursor, queryKey);
if (handler != null) {
let handlingQueryPath = queryPath.concat([ queryKey ]);
let handlingSchemaPath = schemaPath.concat([ schemaKey ]);
let promise = Promise.try(() => {
// This calls the data provider in the schema
return Result.wrapAsync(() => maybeCall(handler, [ args, context ], schemaObject));
return Result.wrapAsync(() => maybeCall(handler, [ args, context ], cursor.schema));
}).then((result) => {
if (result.isOK) {
let value = result.value();
@ -137,18 +123,27 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
return Promise.try(() => {
if (!isLeaf && value != null) {
let effectiveSubquery = (isRecursive)
? { ... queryObject, ... subquery }
? { ... cursor.query, ... subquery }
: subquery;
return mapMaybeArray(value, (item, i) => {
if (i != null) {
let elementQueryPath = handlingQueryPath.concat([i]);
let elementSchemaPath = handlingSchemaPath.concat([i]);
return evaluate(item, effectiveSubquery, context, elementQueryPath, elementSchemaPath);
} else {
return evaluate(item, effectiveSubquery, context, handlingQueryPath, handlingSchemaPath);
}
// NOTE: We're adding `i` to the query path for user feedback purposes, but we're not *actually* diving down into that property on the query object; the queryOverride doesn't just handle recursion, it also ensures that the 'original' subquery is passed in regardless of what the path suggests
// NOTE: schemaOverride here is used to pass in the (asynchronously/lazily) resolved result, which the cursor implementation wouldn't have access to otherwise; need to somehow make it clearer in the API design that the automatic 'schema navigation' is only used for simple objects - maybe not call it an 'override' but instead just something like newSchema and newQuery?
console.log({queryKey, schemaKey});
let subCursor = (i != null)
? cursor
.child(queryKey, schemaKey)
.child(i, i, {
queryOverride: effectiveSubquery,
schemaOverride: item
})
: cursor
.child(queryKey, schemaKey, {
queryOverride: effectiveSubquery,
schemaOverride: item
});
return evaluate(subCursor, context);
});
} else {
// null / undefined are returned as-is, so are leaves
@ -177,7 +172,7 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
}
}).tapCatch((error) => {
// FIXME: Chain properly
assignErrorPath(error, handlingQueryPath, handlingSchemaPath);
assignErrorPath(error, cursor.child(queryKey, schemaKey));
});
return [ queryKey, promise ];
@ -232,8 +227,13 @@ module.exports = function createDLayer(options) {
}
};
let cursor = createCursor({
query: query,
schema: options.schema
});
// FIXME: Currently, top-level errors do not get a path property assigned to them, because that assignment happens on nested calls above
return evaluate(options.schema, query, combinedContext, [], []);
return evaluate(cursor, combinedContext);
}
};
};

Loading…
Cancel
Save