How to use spread properties in Node? - javascript

Below is my code snippet and I would like to use additional argument (.i.e search) for connectionArgs.
const connectionArgs = require('graphql-relay').connectionArgs;
users: {
type: UserConnection.connectionType,
args: {
...connectionArgs,
search: {
type: GraphQLString,
},
},
resolve: async (obj, args, { user }) => {
return UserLoader.loadUsers(user, args);
}
},
Above code works fine when using bable, but I am trying to remove babel (since async await is now supported in node 7.6v).
I get Unexpected token for connectionArgs.
Could any one suggest how to use this spread properties here ?

Related

How to wrap Sequelize result in another object - Adding parent "params" for Nextjs getStaticPaths()

I am trying to fulfill the getStaticPaths of NextJS that demand records to be formatted as:
Which demand the format like so:
[
{
params: {
id: recordId
}
}
]
I can achieve this result with:
const res = await myModel.findAll({
raw: true,
attributes: ['id'],
});
const fixedFormat = res.map(singleRes => {
return {
params: {
id: singleRes.id,
},
};
});
But I was wondering if there's a way that doesn't involve calling map() on such a big array.

NextJs - Additional keys were returned from getStaticPaths

I'm working on a client's project with NextJs,
On the blog section we have different paths, blog/[:category], blog/[:category]/[:post] and blog/author/[:author] And to get this done I'm using getStaticPaths and getStaticProps.
I first fetch all the posts and authors from ContentfulAPI and then loop into them to create a valid path to pass it into the paths array
Ps: It works when I hard code every path individually..
thats my function:
export const getStaticPaths = async () => {
const posts = await DataController.getEntriesByContentType(
"componentBlog",
);
const blogPosts = posts.items.map(item => {
return {params: {blog_post: [item.fields.category.replace(/\s+/g, '-').replace(/'/g, '').toLowerCase(), item.fields.slug]}}
})
const authors = await DataController.getEntriesByContentType(
"author",
);
const authorPaths = authors.items.map(item => {
return {params: {blog_post: ['author', item.fields.slug]}}
})
return {
paths: [
blogPosts,
authorPaths,
],
fallback: false,
}
}
And i get this error when I try to access a blog link :
error - Error: Additional keys were returned from `getStaticPaths` in page "/blog/[...blog_post]". URL Parameters intended for this dynamic route must be nested under the `params` key, i.e.:
return { params: { blog_post: ... } }
Keys that need to be moved: 0, 1, 2, 3, 4, 5, 6, 7, 8.
at C:\Workspace\phoenix-v2\next\new-phoenix\node_modules\next\dist\build\utils.js:518:23
at Array.forEach (<anonymous>)
at Object.buildStaticPaths (C:\Workspace\phoenix-v2\next\new-phoenix\node_modules\next\dist\build\utils.js:492:17) at processTicksAndRejections (internal/process/task_queues.js:95:5) {
type: 'Error',
page: '/blog/[...blog_post]'
}
I'm not sure why I run into this error..
thank you for helping!
You're currently passing an array with the params, use the spread operator (...) to reduce it. Docs
return {
paths: [...blogPosts, ...authorPaths],
...
};

Creating a function from strings

Not sure how 'smart' of an idea this is but I really wanted to experiement with Vue and Javascript and see if I could condense some code a bit (you could say I've done the opposite).
Anways...
I have an data object in vue:
eventOptions: {
eventType: {
data: [],
method: 'getEventTypeList',
service: 'ReportEventService',
},
eventSeverity: {
data: [],
service: 'ReportEventService',
method: 'getSeverityList',
},
eventImpact: {
data: [],
service: 'ReportEventService',
method: 'getBusinessImpactList',
},
eventStatus: {
data: [],
service: 'ReportEventService',
method: 'getEventStatusList',
},
},
And I want to loop through it in the following method and create a function like:
ReportEventService.getEventStatusList() which is referencing an imported javascript file.
async setEventOptions() {
const promises = Object.keys(this.eventOptions).map((key) => {
const { method, service = this.eventOptions[key]
return new Promise(async (resolve, reject) => {
try {
const response = await service[method]();
resolve(response);
} catch (e) {
reject(e);
}
});
});
Promise.all(promises)
.then((responseArray) => {
Object.keys(this.eventOptions).forEach((key, index) => {
this.eventOptions[key]['data'] =
responseArray[index].data;
});
})
.catch((e) => console.log(e));
},
Unfortunately, it's not working.
This line fails:
const callback = service[method]();
Any idea how I can convert two strings to make a function that I can execute? I also understand this undertaking is silly and I can probably just list them out and it would be 10x easier.
I did try:
const func = new Function(`${service}.${method}()`)
The error I get is: TypeError: service[method] is not a function
Object literals can store Object / classes / functions.. etc.
Your currently just storing a string, 'ReportEventService', so when you call the method your doing 'ReportEventService'[method]() and that doesn't make much sense.
But if you store the Object that is ReportEventService instead, you will be calling.. ReportEventService[method]()
IOW: service: ReportEventService,
instead of service: 'ReportEventService',
If you can use any as part of eventOperations, then there is no reason at all to use strings.
Instead make them into callbacks:
eventType: {
data: [],
method: 'getEventTypeList',
service: 'ReportEventService',
},
can become
eventType: {
data: [],
callback: () => ReportEventService.getEventTypeList(),
},
Then you can call it as
const response = await callback();
It you can use a lot more tools to verify your code. At the very least a rename refactoring will work without needing to consider random strings. You can also verify if the method exists or if it is called with the correct number of parameters.
Moreover, you also get more freedom - if you change ReportEventService.getEventTypeList() in the future to require parameters, you can change the callback to () => ReportEventService.getEventTypeList("foo", 42) without having to change the code that consumes it.

Querying child node fields in Gatsby

I have the following GraphQL schema, which defines 3 types: a CondaPackage which hasmany CondaVersion, which hasmany CondaExecutable. I want to be able to query a CondaVersion and ask "how many CondaExecutables do you own which succeeded my analysis". Currently I've written a succeededExeCount and allExeCount which resolve this field by loading all children and manually counting the number of children that succeeded.
exports.createSchemaCustomization = ({ actions: { createTypes }, schema }) => {
createTypes([
schema.buildObjectType({
name: "CondaPackage",
fields: {
succeededExeCount: {
type: "Int!",
resolve(source, args, context){
// TODO
}
},
allExeCount: {
type: "Int!",
resolve(source, args, context){
// TODO
}
}
},
interfaces: ["Node"]
}),
schema.buildObjectType({
name: "CondaVersion",
fields: {
succeededExeCount: {
type: "Float!",
resolve(source, args, context){
const children = context.nodeModel.getNodesByIds({
ids: source.children,
type: "CondaExecutable"
})
return children.reduce((acc, curr) => acc + curr.fields.succeeded, 0)
}
},
allExeCount: {
type: "Int!",
resolve(source, args, context){
return source.children.length;
}
}
},
interfaces: ["Node"]
}),
schema.buildObjectType({
name: "CondaExecutable",
fields: {
succeeded: {
type: "Boolean!",
resolve(source, args, context, info) {
return source.fields.succeeded || false;
}
},
},
interfaces: ["Node"]
})
])
}
My first problem is that this seems incredibly inefficient. For each CondaVersion I'm running a separate query for its children, which is a classic N+1 query problem. Is there a way to tell Gatsby/GraphQL to simply "join" the two tables like I would using SQL to avoid this?
My second problem is that I now need to count the number of succeeding children from the top level type: CondaPackage. I want to ask "how many CondaExecutables do your child CondaVersions own which succeeded my analysis". Again, in SQL this would be easy because I would just JOIN the 3 types. However, the only way I can currently do this is by using getNodesByIds for each child, and then for each child's child, which is n*m*o runtime, which is terrifying. I would like to run a GraphQL query as part of the field resolution which lets me grab the succeededExeCount from each child. However, Gatsby's runQuery seems to return nodes without including derived fields, and it won't let me select additional fields to return. How can I access fields on a node's child's child in Gatsby?
Edit
Here's the response from a Gatsby maintainer regarding the workaround:
Gatsby has an internal mechanism to filter/sort by fields with custom resolvers. We call it materialization. [...] The problem is that this is not a public API. This is a sort of implementation detail that may change someday and that's why it is not documented.
See the full thread here.
Original Answer
Here's a little 'secret' (not mentioned anywhere in the docs at the time of writing):
When you use runQuery, Gatsby will try to resolve derived fields... but only if that field is passed to the query's options (filter, sort, group, distinct).
For example, in CondaVersion, instead of accessing children nodes and look up fields.succeeded, you can do this:
const succeededNodes = await context.nodeModel.runQuery({
type: "CondaExecutable",
query: { filter: { succeeded: { eq: true } } }
})
Same thing for CondaPackage. You might try to do this
const versionNodes = await context.nodeModel.runQuery({
type: "CondaVersion",
query: {}
})
return versionNodes.reduce((acc, nodes) => acc + node.succeededExeCount, 0) // Error
You'll probably find that succeededExeCount is undefined.
The trick is to do this:
const versionNodes = await context.nodeModel.runQuery({
type: "CondaVersion",
- query: {}
+ query: { filter: { succeededExeCount: { gte: 0 } } }
})
It's counter intuitive, because you'd think Gatsby would just resolve all resolvable fields on a type. Instead it only resolves fields that is 'used'. So to get around this, we add a filter that supposedly does nothing.
But that's not all yet, node.succeededExeCount is still undefined.
The resolved data (succeededExeCount) is not directly stored on the node itself, but in node.__gatsby_resolved source. We'll have to access it there instead.
const versionNodes = await context.nodeModel.runQuery({
type: "CondaVersion",
query: { filter: { succeededExeCount: { gte: 0 } } }
})
return versionNodes.reduce((acc, node) => acc + node.__gatsby_resolved.succeededExeCount, 0)
Give it a try & let me know if that works.
PS: I notice that you probably use createNodeField (in CondaExec's node.fields.succeeded?) createTypes is also accessible in exports.sourceNodes, so you might be able to add this succeeded field directly.

graphql passing dynamic data to mutation

haven't used graphql or mongodb previously. What is the proper way to pass objects for the update mutation?
Since the only other way i see to pass multiple dynamically appearing parameters is to use input type which is appears to be a bit ineffective to me (in terms of how it looks in the code, especially with bigger objects), i just pass the possible values themselves. however in this case i need to dynamically construct updateObject, which again, going to get messy for the bigger models.
for example now i did:
Mutation: {
updateHub: async (_, { id, url, ports, enabled }) => {
const query = {'_id': id};
const updateFields = {
...(url? {url: url} : null),
...(ports? {ports: ports} : null),
...(enabled? {enabled: enabled} : null)
};
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
any advise on the better way to handle this?
thanks!
It appears your code could benefit from using ES6 spread syntax -- it would permit you to deal with an arbitrary number of properties from your args object without the need for serial tertiary statements.
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = { ...restArgs };
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
If for some reason you need to explicitly set the undefined properties to null in your object, you could possibly use some a config obj and method like defaults from the lodash library as shown below:
import { defaults } from 'lodash';
const nullFill = { url: null, ports: null, enabled: null }; // include any other properties that may be needed
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = defaults(restArgs, nullFill);
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
Also, FWIW, I would consider placing the dynamic arguments that could be potentially be updated on its own input type, such as HubInput in this case, as suggested in the graphql docs. Below I've shown how this might work with your mutation. Note that because nothing on HubInput is flagged as requird (!) you are able to pass a dynamic collection of properties to update. Also note that if you take this appraoch you will need to properly destructure your args object initially in your mutation, something like { id, input }.
input HubInput {
url: String
ports: // whatever this type is, like [String]
enabled: Boolean
// ...Anything else that might need updating
}
type UpdateHubPayload {
success: Boolean
message: String
hub: Hub // assumes you have defined a type Hub
}
updateHub(id: Int, input: HubInput!): UpdateHubPayload

Categories

Resources