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 // 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 }) { function createInstance({ queryPath, schemaPath, queryObject, schemaObject }) {
console.log("create", { queryPath, schemaPath, queryObject, schemaObject });
return { return {
query: queryPath, queryPath: queryPath,
schema: schemaPath, schemaPath: schemaPath,
child: function (property, { queryOverride, schemaOverride } = {}) { query: queryObject,
schema: schemaObject,
child: function (queryKey, schemaKey, { queryOverride, schemaOverride } = {}) {
return createInstance({ return createInstance({
queryPath: queryPath.concat([ property ]), queryPath: (queryKey != null)
schemaPath: schemaPath.concat([ property ]), ? queryPath.concat([ queryKey ])
queryObject: queryOverride ?? queryObject[property], : queryPath,
schemaObject: schemaOverride ?? schemaObject[property] 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 mapObject = require("map-obj");
const Result = require("../result"); const Result = require("../result");
const createCursor = require("./cursor");
// TODO: Bounded/unbounded recursion // TODO: Bounded/unbounded recursion
// TODO: context // TODO: context
@ -20,19 +21,6 @@ The schema merging will eventually become deep-merging, when multi-level recursi
const specialKeyRegex = /^\$[^\$]/; 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) { function maybeCall(value, args, thisContext) {
return Promise.try(() => { return Promise.try(() => {
// FIXME: Only do this for actual fetch requests // FIXME: Only do this for actual fetch requests
@ -92,44 +80,42 @@ function analyzeSubquery(subquery) {
return { isRecursive, allowErrors, hasChildKeys, isLeaf, args }; return { isRecursive, allowErrors, hasChildKeys, isLeaf, args };
} }
function analyzeQueryKey(schemaObject, queryObject, queryKey) { function analyzeQueryKey(cursor, queryKey) {
let subquery = queryObject[queryKey]; let childCursor = cursor.child(queryKey, null);
let schemaKey = subquery?.$key ?? queryKey; // $key is for handling aliases let schemaKey = childCursor.query?.$key ?? queryKey; // $key is for handling aliases
let handler = schemaObject[schemaKey] ?? schemaObject.$anyKey; 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 { return {
... analyzeSubquery(subquery), ... analyzeSubquery(childCursor.query),
schemaKey: schemaKey, schemaKey: schemaKey,
handler: handler handler: handler
}; };
} }
function assignErrorPath(error, queryPath, schemaPath) { function assignErrorPath(error, cursor) {
if (error.path == null) { 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 // 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.path = cursor.queryPath;
error.message = error.message + ` (${stringifyPath(queryPath, schemaPath)})`; error.message = error.message + ` (${cursor.toPathString()})`;
} }
} }
function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) { function evaluate(cursor, context) {
// map query object -> result object // map query object -> result object
return asyncMapObject(queryObject, (queryKey, subquery) => { return asyncMapObject(cursor.query, (queryKey, subquery) => {
let shouldFetch = (subquery !== false); let shouldFetch = (subquery !== false);
if (!shouldFetch || specialKeyRegex.test(queryKey)) { 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. // 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; return mapObject.mapObjectSkip;
} else { } 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) { if (handler != null) {
let handlingQueryPath = queryPath.concat([ queryKey ]);
let handlingSchemaPath = schemaPath.concat([ schemaKey ]);
let promise = Promise.try(() => { let promise = Promise.try(() => {
// This calls the data provider in the schema // 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) => { }).then((result) => {
if (result.isOK) { if (result.isOK) {
let value = result.value(); let value = result.value();
@ -137,18 +123,27 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
return Promise.try(() => { return Promise.try(() => {
if (!isLeaf && value != null) { if (!isLeaf && value != null) {
let effectiveSubquery = (isRecursive) let effectiveSubquery = (isRecursive)
? { ... queryObject, ... subquery } ? { ... cursor.query, ... subquery }
: subquery; : subquery;
return mapMaybeArray(value, (item, i) => { return mapMaybeArray(value, (item, i) => {
if (i != null) { // 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
let elementQueryPath = handlingQueryPath.concat([i]); // 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?
let elementSchemaPath = handlingSchemaPath.concat([i]); console.log({queryKey, schemaKey});
let subCursor = (i != null)
return evaluate(item, effectiveSubquery, context, elementQueryPath, elementSchemaPath); ? cursor
} else { .child(queryKey, schemaKey)
return evaluate(item, effectiveSubquery, context, handlingQueryPath, handlingSchemaPath); .child(i, i, {
} queryOverride: effectiveSubquery,
schemaOverride: item
})
: cursor
.child(queryKey, schemaKey, {
queryOverride: effectiveSubquery,
schemaOverride: item
});
return evaluate(subCursor, context);
}); });
} else { } else {
// null / undefined are returned as-is, so are leaves // null / undefined are returned as-is, so are leaves
@ -177,7 +172,7 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
} }
}).tapCatch((error) => { }).tapCatch((error) => {
// FIXME: Chain properly // FIXME: Chain properly
assignErrorPath(error, handlingQueryPath, handlingSchemaPath); assignErrorPath(error, cursor.child(queryKey, schemaKey));
}); });
return [ queryKey, promise ]; 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 // 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