@ -15,6 +15,7 @@ const loadModules = require("./load-modules");
// FIXME: $getProperty, $getPropertyPath, maybe $resolveObject/$query?
// FIXME: Allow setting an evaluation depth limit for queries, to limit eg. recursion
// FIXME: recurseDepth, recurseLabel/recurseGoto
// TODO: Internal queries, but only in modules, and only as a last resort
/ * P r o c e s s d e s i g n :
@ -38,17 +39,28 @@ The schema merging will eventually become deep-merging, when multi-level recursi
const specialKeyRegex = /^\$[^\$]/ ;
function maybeCall ( value , args , thisContext) {
function maybeCall ( value , args , baseContext, getContextForModule , thisContext) {
return Promise . try ( ( ) => {
// TODO" Is the $get thing still relevant?
// FIXME: Only do this for actual fetch requests
let getter = ( typeof value === "object" && value != null && value . $get != null )
? value . $get
: value ;
if ( typeof getter === "function" ) {
return getter . call ( thisContext , ... args ) ;
let actualGetter = ( getter . _ _moduleID != null )
? getter . func
: getter ;
if ( typeof actualGetter === "function" ) {
let applicableContext = ( getter . _ _moduleID != null )
// Defined in a module
? getContextForModule ( getter . _ _moduleID )
// Defined in the root schema
: baseContext ;
return actualGetter . call ( thisContext , args , applicableContext ) ;
} else {
return getter ;
return actualG etter;
}
} ) ;
}
@ -121,18 +133,12 @@ function assignErrorPath(error, cursor) {
function makeEnvironment ( context , getContextForModule ) {
function callHandler ( instruction ) {
// NOTE: cursor is assumed to already be the key of the child
let { schemaKey, handler, args , allowErrors , cursor } = instruction ;
let { handler, args , allowErrors , cursor } = instruction ;
if ( handler != null ) {
return Promise . try ( ( ) => {
// This calls the data provider in the schema
if ( handler . _ _moduleID != null ) {
// Defined in a module
return Result . wrapAsync ( ( ) => maybeCall ( handler . func , [ args , getContextForModule ( handler . _ _moduleID ) ] , cursor . parent . schema ) ) ;
} else {
// Defined in the root schema
return Result . wrapAsync ( ( ) => maybeCall ( handler , [ args , context ] , cursor . parent . schema ) ) ;
}
return Result . wrapAsync ( ( ) => maybeCall ( handler , args , context , getContextForModule , cursor . parent . schema ) ) ;
} ) . then ( ( result ) => {
if ( result . isOK ) {
return result . value ( ) ;
@ -154,7 +160,7 @@ function makeEnvironment(context, getContextForModule) {
assignErrorPath ( error , cursor ) ;
} ) ;
} else {
throw new Error ( ` No key ' ${ schemaKey } ' exists in the schema ` ) ;
throw new Error ( ` No key ' ${ cursor. schemaPath . at ( - 1 ) } ' exists in the schema ` ) ;
}
}
@ -234,6 +240,36 @@ module.exports = function createDLayer(options) {
? options . makeContext ( )
: { } ;
// NOTE: The code order is important here - there's a cyclical reference between getProperty and the combinedContext, and only getProperty gets hoisted!
let combinedContext = {
... generatedContext ,
... context ,
// FIXME: Figure out a way to annotate errors here with the path at which they occurred, *and* make clear that it was an internal property lookup
$getProperty : getProperty ,
$getPropertyPath : function ( object , propertyPath ) {
let parsedPath = ( typeof propertyPath === "string" )
? propertyPath . split ( "." )
: propertyPath ;
return Promise . reduce ( parsedPath , ( currentObject , pathSegment ) => {
if ( currentObject != null ) {
return getProperty ( currentObject , pathSegment ) ;
} else {
// Effectively null-coalescing
return null ;
}
} , object ) ;
} ,
$make : function ( typeID , args ) {
return make ( typeID , args , true ) ;
} ,
$maybeMake : function ( typeID , args ) {
return make ( typeID , args , false ) ;
}
} ;
let getContextForModule = loaded . makeContextFactory ( combinedContext ) ;
function getProperty ( object , property , args = { } ) {
// TODO: Should this allow a single-argument, property-string-only variant for looking up properties on self?
// FIXME: Validatem
@ -242,7 +278,7 @@ module.exports = function createDLayer(options) {
}
if ( property in object ) {
return maybeCall ( object [ property ] , [ args , combinedContext ] , object ) ;
return maybeCall ( object [ property ] , args , combinedContext , getContextForModule , object ) ;
} else {
// FIXME: Better error message with path
throw new Error ( ` No key ' ${ property } ' exists in the schema ` ) ;
@ -271,40 +307,13 @@ module.exports = function createDLayer(options) {
return instance ;
}
}
let combinedContext = {
... generatedContext ,
... context ,
// FIXME: Figure out a way to annotate errors here with the path at which they occurred, *and* make clear that it was an internal property lookup
$getProperty : getProperty ,
$getPropertyPath : function ( object , propertyPath ) {
let parsedPath = ( typeof propertyPath === "string" )
? propertyPath . split ( "." )
: propertyPath ;
return Promise . reduce ( parsedPath , ( currentObject , pathSegment ) => {
if ( currentObject != null ) {
return getProperty ( currentObject , pathSegment ) ;
} else {
// Effectively null-coalescing
return null ;
}
} , object ) ;
} ,
$make : function ( typeID , args ) {
return make ( typeID , args , true ) ;
} ,
$maybeMake : function ( typeID , args ) {
return make ( typeID , args , false ) ;
}
} ;
let cursor = createCursor ( {
query : query ,
schema : schema
} ) ;
let evaluate = makeEnvironment ( combinedContext , loaded. makeContextFactory ( combinedContext ) ) ;
let evaluate = makeEnvironment ( combinedContext , getContextForModule ) ;
// FIXME: Currently, top-level errors do not get a path property assigned to them, because that assignment happens on nested calls above
return evaluate ( cursor ) ;