Graphql-js Custom Directive execute in Pipeline - node.js

i try to get my head around about custom directives. I use the graphql from neo4j. But I think it is a general question. I need to do the following:
Query neo4j and construct a string, that serves as an ID. So I use there #cypher directive. This returns a string
then use this ID and request another endpoint use my custom directive. This will return an array of small objects.
Its basically piping one result of a directive to the other. When writing the transform function for the schema to allow my custom directive, the fieldConfig.resolve-Function does not get called, when its an graphql interface or type. If i would set the return type of my test field to String, then the resolve function is executed.
In the resolve function I do my rest request and return the result. Obviously the result is now an array of objects and not a string. Therefore graphql complains about mismatching types.
directive #Rest on FIELD_DEFINITION
type Query {
test: ResultType #Rest #cypher(statement """ some cypher query return string """)
}
type ResultType {
timestamp: Float
unit: String
value: String
}
(schema: GraphQLSchema) =>
mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const dirs = getDirective(schema, fieldConfig, directiveName);
const fieldDirective = dirs?.[0];
if (fieldDirective) {
const resolve = fieldConfig.resolve || defaultFieldResolver;
fieldConfig.resolve = async (source, args, context, info) => {
const result = await resolve(source, args, context, info);
const arr = await getArgumentValues(source, args, context);
if (typeof result === "string") {
try {
const response = await axios.get("http://some-rest-endpoint");
const data= response.data || [];
return data;
} catch (e) {
console.error(e);
return [];
}
}
return [];
};
}
return fieldConfig;
},
});
Is there a solution or a hint to manage what I try to achieve?

Related

Query a DynamoDB table while passing a parameter nested within a forEach() method of an array

I'm scanning all items from a DynamoDB table - within a Lambda function - with DocumentClient. I'm then looping through each item and extracting the payload that I need. I'll use that item from the payload as a parameter with ExpressionAttributeValues in a new query.
Everything works dandy independently. The issue is with the use of the asynchronous function queryItems when nested within an array forEach() method. I getting a parsing error with the function queryItems. I can query the table when I call the function outside of the loop but how else am I going to query each item independently?
I'm not sure how to handle this.
'use strict';
const aws = require('aws-sdk');
const docClient = new aws.DynamoDB.DocumentClient();
var paramsAll = {
TableName: 'MyTable',
Select: "ALL_ATTRIBUTES"
};
exports.handler = async (event, context) => {
try {
let arr = [];
let sequence = '';
//scan all items in table
docClient.scan(paramsAll, function(err, data) {
if (err) {
//handle error
}
else {
//Loop through each item in the table:
let items = (data.Items);
items.forEach(function(Item) {
let p = (Item.payload);
//Extract sequence from the payload
sequence = (p.seq);
arr.push(sequence);
//perform other function with this array (not listed for brevity)
});
//Here is where I'm having the issue:
arr.forEach(function(Item) {
//Pass these items as a paramater within queryItems function but getting Parsing Error: unexpected token queryItems
const results = await queryItems(Item);
//do something with the results...
})
}
});
}
catch (err) {
return { error: err };
}
};
async function queryItems(p) {
try {
var params = {
TableName: 'MyTable',
KeyConditionExpression: '#seq = :value',
ExpressionAttributeValues: { ':value': p },
ExpressionAttributeNames: { '#seq': 'seq' }
};
const data = await docClient.query(params).promise();
return data;
}
catch (err) {
return err;
}
}
I've definitely run into a similar issue. What I believe is happening is just a Javascript syntax issue, where awaiting queryItems inside the synchronous function provided to forEach will produce an error. (Although, when running the code, I do get the specific error "SyntaxError: await is only valid in async functions and the top level bodies of modules", so there might be something else going on.)
I see nothing wrong with the DynamoDB queries, but hoangdv's suggestions are spot on. Specifically, I'd also suggest using the promise style for scan, and while a for...loop will definitely work, using Promise.all and map will be a lot quicker to complete all the queries. Here's how I'd modify the code:
'use strict';
const aws = require('aws-sdk');
const docClient = new aws.DynamoDB.DocumentClient();
// avoid var unless you specifically require it's hoisting behavior.
const paramsAll = {
TableName: 'MyTable',
Select: "ALL_ATTRIBUTES" // most likely not needed, I'd review this section of the docs: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Select
};
exports.handler = async (event, context) => {
try {
// unless you need to assign a new array to this variable, it is better practice to use const instead.
const arr = [];
// let sequence = ''; // see line 24 for why I commented this out.
// scan all items in table.
// Destructure Items out of the response.
// You may also need to continue scanning with the LastEvaluatedKey depending on the size of your table, and/or your use case.
// You'd continue scanning in a while loop, for example.
const { Items, LastEvaluatedKey } = await docClient.scan(paramsAll).promise();
// push the sequence to the arr.
// There is most likely a reason you omitted for brevity to have sequence defined above,
// but since this example doesn't need it above, I've omitted it entirely
Items.forEach(Item => {
const p = Item.payload;
arr.push(p.seq);
});
// use a for loop or map here instead. forEach will return undefined, which cannot be await'ed.
// instead, map will return a new array of Promises (since the callback is async).
// Then, you can use Promise.all to await until each Promise in the array is resolved.
// Keep in mind, depending on how many items you are iterating through, you may run into DynamoDB's ThrottlingException.
// You would have to batch the queries (in other words, split the arr into pieces, and iterate over each piece), which would have to be done before using map. Then, sleep for a few milliseconds before starting on the next piece.
// I doubt the queries will be quick enough to cause this when using a for loop, though.
await Promise.all(arr.map(async Item => {
const results = await queryItems(Item);
// do something with the results...
}));
}
catch (err) {
// Again, not sure what the use case is, but just FYI this is not a valid return value if this lambda function is intended for use with using API Gateway.
// See here :) https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#apigateway-types-transforms
return { error: err };
}
};
// Presumably, MyTable has a partitionKey of seq, otherwise this KeyConditionExpression is invalid.
async function queryItems(p) {
try {
var params = {
TableName: 'MyTable',
KeyConditionExpression: '#seq = :value',
ExpressionAttributeValues: { ':value': p },
ExpressionAttributeNames: { '#seq': 'seq' }
};
const data = await docClient.query(params).promise();
return data;
}
catch (err) {
return err;
}
}
Your issue is how you await on the for loop, its best to use Promise.all() with a map to await inside of a loop:
await Promise.all(arr.map(async Item => {
const results = await queryItems(Item);
// do something with the results...
}));
However, I cannot seem to understand your logic really well.
You Scan a table called MyTable, but you do not paginate, meaning you are only getting up to 1MB worth of data.
With the results, you strip out the seq value and then once again read every item from MyTable this time using a Query and seq as the key?

typescript infer never[] when returned empty array from catch statement with async/await method

method getData() returns Promise<Output[]>
without catch statement type of data inferred is Output[]
but when I try to put catch statement in front of getData() method type of data becomes Output[] | void and I can understand that if there is an error while getting data then I am not returning anything from catch block.
When I try to return an empty array from catch statement type of data that typescript is inferring is Output[] | never[]. I am not able to understand the concept of never[]
I am not able to grasp the concept of never[]. and what would be the best practice/solution in this case to handle error and get the intended type of array instead of void or never.
const data = await getData().catch((e) => {
handleError(e);
return [];
});
when you're using async/await, the general approach is to wrap it with try/catch. This helps with typescript inferring the type.
For example,
async function getData() {
return [1];
}
// inferred as `number[] | never[]` because .catch adds additional types.
// The returned value could be the type from getData or the type from the catch function.
// Promise<number[]>.catch<never[]>
const data = await getData().catch(e => {
...
})
Whereas if you wrap it with try/catch
try {
// inferred as `number[]`
const data = await getData();
return data;
} catch (e) {
handleError(e);
return [];
}
Here is a full example.
async function getData() {
return [1];
}
function handleError(e: any) {
console.log(e);
}
async function main() {
try {
const data = await getData();
return data;
} catch (e) {
handleError(e);
return [];
}
}
main();
When you return [], TS assumes that it'll always be an empty array. It's a tuple type with 0 elements. That's why you get a never[]. Since you'll never be able to access any of its elements.
You can change it to:
const data = await getData().catch((e) => {
handleError(e);
return [] as Output[];
});

How to return Data from readFileAsync

I have a function that needs to read from a file, then convert it to an json object with JSON.parse(string) and lastly return it to be used elsewhere.
this object is written to a txt file using JSON.stringify():
let User = {
Username:"#",
Password:"#",
FilePaths:[
{Year:'#', Month:'#', FileName:'#'}
]
}
the writing works fine, my problem is the reading,
Here is my code:
const readFileAsync = promisify(fs.readFile);
async function GetUserData(User) {
const Read = async () => {
return await (await readFileAsync('UserData\\'+User.Username+'\\UserData.txt')).toString();
}
return Read().then(data=>{return JSON.parse(data)})
}
console.log(GetUserData(User));
The result I get back is :
Promise {[[PromiseState]]: 'pending', [[PromiseResult]]: undefined}
How can I make this work so
In this instance, GetUserData returns a promise, so you'll need to use .then to get the data once resolved.
GetUserData(User).then(data => {
// do something with data
});
Alternatively, fs has a sync function you can use, rather relying on readFile (which is async), use readFileSync. Usage below:
function GetUserData(User) {
const data = fs.readFileSync('UserData\\'+User.Username+'\\UserData.txt').toString();
return JSON.parse(data);
}
console.log(GetUserData(User));

TypeError: obj.hasOwnProperty is not a function when calling Graphql mutation

I get a strange error and can't figure out what I am doing wrong. I wrote a graphql mutation to call an api:
domainStuff: async (parent, { command, params }, { models }) => {
console.log("Params:", params);
const result = await dd24Api(command, params);
return result;
}
This it the function I call:
export default async (command, parameter) => {
const args = _.merge({ params: parameter }, auth);
// Eleminate copying mistakes
console.log(typeof args);
const properCommand = command + "Async";
const result = await soap
.createClientAsync(apiWSDL)
.then(client => {
return client[properCommand](args)
.then(res => {
console.log(res[command + "Result"]);
return res[command + "Result"];
})
.catch(err => {
console.log(err);
return err;
});
})
.catch(err => console.log(err));
return result;
with this query variables:
{
"command": "CheckDomain",
"params": {"domain": "test.it"}
}
The console.log shows me that args is an object, but I get this error (from the first catch block):
TypeError: obj.hasOwnProperty is not a function
How can that be? After all I checked whether it is an object and it is. More strange, if I give a hardcoded object into the query, this for example:
domainStuff: async (parent, { command, params }, { models }) => {
console.log("Params:", params);
const result = await dd24Api(command, {domain: "test.com"});
return result;
}
then it works perfectly fine. What am I doing wrong? Thx for any help in advance.
EDIT: I am using "graphql-server-express": "^0.8.0" and "graphql-tools": "^1.0.0"
If you are using graphql-js there are a lot of places where new objects are created using Object.create(null) which is different from {} you can read an explanation about that here Creating Js object with Object.create(null)?
But essentially an object created with Object.create(null) has no hasOwnProperty method
You can try it in node with
const obj1 = Object.create(null)
console.log(typeof obj1.hasOwnProperty)
// 'undefined'
const obj2 = {}
console.log(typeof obj2.hasOwnProperty)
// 'function'
Another method you can use for determining if an object has a key that will work on an object created with Object.create(null) is
function hasKey(obj, key) {
return Object.keys(obj).indexOf(key) !== -1
}
You can convert it to a standard Object by:
data = JSON.parse(JSON.stringify(data));
Okay, after an extensive period of trial and error I was able to fix the problem. Changing the position of the objects I pass into the .merge function was the solution. The working code is:
const args = _.merge(auth, { params: parameter });
Both are objects, but it seems like the object coming from the graphql variables did not have the required hasOwnProperty method. Simply speaking, instead of copying the "good object" (auth) into the bad one (parameter), I copy the bad one into the good one which has the needed function.
Hopefully this helps someone else who also experiences this error.

Axios GET is sending the same url multiple times in Promise chain

I have a Promise chain that runs like this:
// this part is not meant to be syntactically correct
axios.get(<rest_api_that_queries_a_list_of_car_models>).then(res => {
// loop thru list and call a custom module promise
for (...) {
mymodule.getSomething(args).then(res => {
axios.post(<rest_write_to_db>).then(res => {
//we're done
....
// in mymodule
function getSomething(args) {
return getAnotherThing(args).then(res => {
// do stuff
return aThing
...
function getAnotherThing(args) {
return getThatThing(args).then(res => {
// see if pagination is greater than 1 page
if (pages == 1)
return res
let promises = [res]
for (x=2;x<pages;x++) {
// change args
promises.push( getThatThing(args))
}
return Promise.all(promises)
}).then(allres => {
return allres
})
...
// this is where it's breaking. this part is syntactically accurate
function getThatThing(args) {
let params = Object.assign(BASE_PARAMS, args.params)
console.log(args.params.model) // this logs prints a different model everytime
return axios.get(URL, {
headers: {
"Accept": ACCEPT,
"Content-Type":CONTENT_TYPE,
},
params: params
}).then (response => {
console.log(response.request.path) // this path includes the last key only everytime. so if there are 10 car models, this will search for the last model 10 times.
let result = response.data
return result
}).catch(function (error) {
console.log("search error:",error);
return error.response.data.errorMessage[0].error[0].message[0]
})
}
So basically the issue is that the axios.get command in the last function is using the same get parameters even tho I'm printing different parameters right before I make the call. I don't see how that is possible.
I was able to fix the issue by changing this line
let params = Object.assign(BASE_PARAMS, args.params)
to this
let params = {...BASE_PARAMS, ...args.params}
I can't really tell you why this fixed it. I'm assuming the Object.assign set the value to params on a global level. Perhaps someone else could provide more insight.

Resources