Sequelize - return object from 2nd level chain after last callback execution is complete - callback

in the addBussiness method, I create a business object and then use the result to create a UserBusiness object in the database using sequelize.
I want to return the Business object created after the UserBusiness object is done creating.
As it stands now I think due to the asynchronous nature of Javascript, the business object is returned before the UserBusiness object is done creating.
Is there a way to return the object from the 1st callback in the 2nd callback?
addBusiness(_,args) {
// this is needed to put it correctly in the geometry point format that the database expects
var point = { type: 'Point', coordinates: [args.input.location.longitude,args.input.location.latitude] };
args.input.location = point;
return Business.create(args.input, {
include: [{
association: Hours,
as: 'hours'
}]
}).then(business => {
UserBusiness.create({userId: args.input.userId, businessId: business.businessId})
return business; // this might get returned before UserBusiness.create is done executing
})
},
here is my mapping defined in sequelized:
UserAccountModel.belongsToMany(BusinessModel, { through: UserBusinessModel, foreignKey: 'user_id'});
BusinessModel.belongsToMany(UserAccountModel, { through: UserBusinessModel , foreignKey: 'business_id'});

You can return UserBusiness object after creation and then return UserBusiness.bussiness or save business object in a variable and return the variable after UserBusiness object is created.
addBusiness(_,args) {
var point = { type: 'Point', coordinates: [args.input.location.longitude,args.input.location.latitude] };
args.input.location = point;
return Business.create(args.input, {
include: [{
association: Hours,
as: 'hours'
}]
}).then(business => {
return UserBusiness.create({userId: args.input.userId, businessId: business.businessId})
}).then(UserBusiness => {
return UserBusiness.business;
})
}
OR
addBusiness(_,args) {
var _business;
var point = { type: 'Point', coordinates: [args.input.location.longitude,args.input.location.latitude] };
args.input.location = point;
return Business.create(args.input, {
include: [{
association: Hours,
as: 'hours'
}]
}).then(business => {
_business = business;
return UserBusiness.create({userId: args.input.userId, businessId: business.businessId})
})
.then(() => {
return _business;
})
}

Related

Different Read/Write types for FirestoreDataConverter

Is there a way to use different types for reading and writing data using the FirebaseDataConverter?
The typing of FirebaseDataConverter<T> suggest that there should only be a single type T, which is both what you would get back when querying and what you should provide when writing.
But in the scenario outlined below, I have two types, InsertComment which is what I should provide when creating a new comment, and Comment, which is an enriched object that has the user's current name and the firebase path of the object added to it.
But there is no way to express that I have these two types. Am I missing something?
type Comment = { userId: string, userName: string, comment: string, _firebasePath: string }
type InsertComment = { userId: string, comment: string }
function lookupName(_id: string) { return 'Steve' }
const commentConverter: FirestoreDataConverter<Comment> = {
fromFirestore(snapshot, options) {
const { userId, comment } = snapshot.data(options)
return {
userId,
comment,
name: lookupName(userId),
_firebasePath: snapshot.ref.path,
} as any as Comment
},
// Here I wish I could write the below, but it gives me a type error
// toFirestore(modelObject: InsertComment) {
toFirestore(modelObject) {
return modelObject
},
}
const commentCollection = collection(getFirestore(), 'Comments').withConverter(commentConverter)
// This works great and is typesafe
getDocs(commentCollection).then(snaps => {
snaps.docs.forEach(snap => {
const { comment, userName, _firebasePath } = snap.data()
console.info(`${userName} said "${comment}" (path: ${_firebasePath})`)
})
})
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This gives me the type-error: that fields "userName, _firebasePath" are missing
addDoc(commentCollection, { comment: 'Hello World', userId: '123' })
I found a workaround, but I don't think this ought to be the way it should be done. It feels hacky.
Basically, I make two DataConverters, one for reading and one for writing.
I make the one for reading the default one, and when I need to write, I overwrite the read-converter with the write-converter.
function createReadFirestoreConverter<T>(validator: Validator<T>): FirestoreDataConverter<T> {
return {
fromFirestore(snapshot, options) {
return validator({ ...snapshot.data(options), _id: snapshot.id, _path: snapshot.ref.path })
},
toFirestore() {
throw new Error('Firestore converter not configured for writing')
},
}
}
function createWriteFirestoreConverter<T>(validator: Validator<T>) {
return {
fromFirestore() {
throw new Error('Firestore converter not configured for reading')
},
toFirestore(modelObject: any) {
return validator(modelObject)
},
} as FirestoreDataConverter<any>
}
const installedComponentConverterRead = createReadFirestoreConverter(installedComponentValidator)
const installedComponentConverterWrite = createWriteFirestoreConverter(newInstalledComponentValidator)
const readCollection = collection(getFirestore(), `MachineCards/${machineCard._id}/Components`).withConverter(installedComponentConverterRead)
// If I need to write
const docRef = doc(readCollection, 'newDocId').withConverter(installedComponentConverterWrite)

Why does GraphQl return null?

I am trying to create an API with MongoDB, Express Js, Node and GraphQl. I have a collection called characters, with the following schema:
const CharacterSchema = Schema({
page:{
type: Number,
required: true
},
data:{
type: Array,
required: true
}
});
I have 25 objects in my database with the above schema. I have a query to query the characters, passing the page number by parameter:
type Character {
_id: ID
name: String!
status: String!
species: String!
type: String!
gender: String!
origin: String!
image: String!
episode: [String]
location: String!
created: String!
}
type Page {
page: Int!
data: [Character]!
}
type Query {
characters(page: Int!): Page!
}
And this is its resolver:
export const resolvers = {
Query: {
characters: async (_, args) => {
let data = await Character.findOne({ page: args.page });
return data;
},
},
};
This is the query Im using to fetch the data:
query($page: Int!) {
characters(page: $page) {
page
data {
name
status
species
type
gender
origin
image
episode
location
created
}
}
}
Executing the query by passing the page number, it returns perfectly the information I ask for.
Now I want to get only one character by its ID. I created a query and a type to fetch only one character by its id:
type CharacterById {
result: Character
}
type Query {
characters(page: Int!): Page!,
character(id: ID): CharacterById
}
This is its resolver:
export const resolvers = {
Query: {
//this works perfectly
characters: async (_, args) => {
let data = await Character.findOne({ page: args.page });
return data;
},
//returns obj but show me null
character: async (_, args) => {
//first method returns the object perfectly
let data = await Character.aggregate([
{ $unwind: "$data" },
{ $match: { "data._id": args.id } },
]);
return data[0].data // returns object
//second method returns the object perfectly
let data = await Character.findOne({"data._id": args.id})
let character = data.data.find(item => item._id === args.id)
return character // returns object
},
},
};
I explain the above: The query “character” is the resolver that I created to get from the database the character with the id passed by parameter.
I try it with two methods. Both of them return me perfectly the object with the id passed by parameter, but when I try to use the query:
query($characterId: ID!) {
character(id: $characterId) {
result {
name
status
species
type
gender
origin
image
episode
location
created
}
}
}
It returns me a null, when it should return me the object:
{
"data": {
"character": null
}
}
why doesn't it bring me the object?
please help me I am very stressed and frustrated that this is not working for me :(

What happens behind the scene when you update a nested array element in a MongoDB Document

When I do a nested object update inside an array in a Document. Does Mongo DB Engine needs to fetch and parse the whole document update the field and reinsert the document ?
db.ControllerPointCollection.updateOne({
"_id": "Ashutosh Das_MigrationTest_0_1_0"
}, {
$set: {
"Tables.$[t].Blocks.$[b].Points.$[p].Description": "Hey You"
}
}, {
arrayFilters: [{
"t.ID": 32
}, {
"b.ID": 268
}, {
"p.PointDefinitionID": 280
}]
})
Behind the scene, mongodb has a class called Model and inside Model class compose other behaviours with initializing other classes and one of them, I call it Sync which is implemented like this. this is not exact code, but you get the idea:
interface HasId {
id?: number; //optional
}
export class ApiSync<T extends HasId> {
constructor(public rootUrl: string) {}
// if user has Id, that means it is already stored in db, so we make a put request, if it does not then we make post
// so in mongoose, saving means Http request to db
save(data: T): AxiosPromise {
const { id } = data;
if (id) {
return axios.put(this.rootUrl + id, data);
} else {
return axios.post(this.rootUrl, data);
}
}
fetch(id: number): AxiosPromise {
return axios.get(this.rootUrl + id);
}
}

How to avoid unexpected side effect in computed properties - VueJS

I am trying to prefill a form with data from a vuex store.In the code provided is the expected result, I need but I know that this is not the way to do it. I am fairly new to Vue/Vuex. The inputs use a v-model thats why i cant use :value="formInformation.parentGroup" to prefill.
data() {
return {
groupName: { text: '', state: null },
parentName: { text: '', state: null },
};
},
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
this.groupName.text = formInfo.name // Is there a way to not use this unexpected side effect ?
return formInfo;
} else {
return 'No Form Selected';
}
},
},
I searched for an answere for so long now that i just needed to ask it. Maybe i am just googling for something wrong, but maybe someone here can help me.
You are doing all right, just a little refactoring and separation is needed - separate all the logic to computed properties (you can also use mapGetters):
mounted() {
if (this.formInformation) {
this.$set(this.groupName.text, this.formInformation.name);
}
},
computed: {
groups() {
return this.$store.getters.groups;
},
activeForm() {
return this.$store.getters.activeForm;
},
formInformation() {
if (this.activeForm.groupIndex) {
return this.groups[0][this.activeForm.groupIndex][
this.activeForm.formIndex
];
}
}
}
You could either make groupName a computed property:
computed: {
groupName() {
let groupName = { text: '', state: null };
if (formInformation.name) {
return groupName.text = formInfo.name;
}
return groupName;
}
Or you could set a watcher on formInformation:
watch: {
formInformation: function (newFormInformation, oldFormInformation) {
this.groupName.text = formInfo.name;
}
},
Avoid mutating data property in computed.
Computed are meant to do some operation (eg. reduce, filter etc) on data properties & simply return the result.
Instead, you can try this:
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
// this.groupName.text = formInfo.name // <-- [1] simply, remove this
return formInfo;
} else {
return 'No Form Selected';
}
}
},
// [2] add this, so on change `formInformation` the handler will get called
watch: {
formInformation: {
handler (old_value, new_value) {
if (new_value !== 'No Form Selected') { // [3] to check if some form is selected
this.groupName.text = new_value.name // [4] update the data property with the form info
},
deep: true, // [5] in case your object is deeply nested
}
}
}

How to implement a node query resolver with apollo / graphql

I am working on implementing a node interface for graphql -- a pretty standard design pattern.
Looking for guidance on the best way to implement a node query resolver for graphql
node(id ID!): Node
The main thing that I am struggling with is how to encode/decode the ID the typename so that we can find the right table/collection to query from.
Currently I am using postgreSQL uuid strategy with pgcrytpo to generate ids.
Where is the right seam in the application to do this?:
could be done in the primary key generation at the database
could be done at the graphql seam (using a visitor pattern maybe)
And once the best seam is picked:
how/where do you encode/decode?
Note my stack is:
ApolloClient/Server (from graphql-yoga)
node
TypeORM
PostgreSQL
The id exposed to the client (the global object id) is not persisted on the backend -- the encoding and decoding should be done by the GraphQL server itself. Here's a rough example based on how relay does it:
import Foo from '../../models/Foo'
function encode (id, __typename) {
return Buffer.from(`${id}:${__typename}`, 'utf8').toString('base64');
}
function decode (objectId) {
const decoded = Buffer.from(objectId, 'base64').toString('utf8')
const parts = decoded.split(':')
return {
id: parts[0],
__typename: parts[1],
}
}
const typeDefs = `
type Query {
node(id: ID!): Node
}
type Foo implements Node {
id: ID!
foo: String
}
interface Node {
id: ID!
}
`;
// Just in case model name and typename do not always match
const modelsByTypename = {
Foo,
}
const resolvers = {
Query: {
node: async (root, args, context) => {
const { __typename, id } = decode(args.id)
const Model = modelsByTypename[__typename]
const node = await Model.getById(id)
return {
...node,
__typename,
};
},
},
Foo: {
id: (obj) => encode(obj.id, 'Foo')
}
};
Note: by returning the __typename, we're letting GraphQL's default resolveType behavior figure out which type the interface is returning, so there's no need to provide a resolver for __resolveType.
Edit: to apply the id logic to multiple types:
function addIDResolvers (resolvers, types) {
for (const type of types) {
if (!resolvers[type]) {
resolvers[type] = {}
}
resolvers[type].id = encode(obj.id, type)
}
}
addIDResolvers(resolvers, ['Foo', 'Bar', 'Qux'])
#Jonathan I can share an implementation that I have and you see what you think. This is using graphql-js, MongoDB and relay on the client.
/**
* Given a function to map from an ID to an underlying object, and a function
* to map from an underlying object to the concrete GraphQLObjectType it
* corresponds to, constructs a `Node` interface that objects can implement,
* and a field config for a `node` root field.
*
* If the typeResolver is omitted, object resolution on the interface will be
* handled with the `isTypeOf` method on object types, as with any GraphQL
* interface without a provided `resolveType` method.
*/
export function nodeDefinitions<TContext>(
idFetcher: (id: string, context: TContext, info: GraphQLResolveInfo) => any,
typeResolver?: ?GraphQLTypeResolver<*, TContext>,
): GraphQLNodeDefinitions<TContext> {
const nodeInterface = new GraphQLInterfaceType({
name: 'Node',
description: 'An object with an ID',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The id of the object.',
},
}),
resolveType: typeResolver,
});
const nodeField = {
name: 'node',
description: 'Fetches an object given its ID',
type: nodeInterface,
args: {
id: {
type: GraphQLID,
description: 'The ID of an object',
},
},
resolve: (obj, { id }, context, info) => (id ? idFetcher(id, context, info) : null),
};
const nodesField = {
name: 'nodes',
description: 'Fetches objects given their IDs',
type: new GraphQLNonNull(new GraphQLList(nodeInterface)),
args: {
ids: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))),
description: 'The IDs of objects',
},
},
resolve: (obj, { ids }, context, info) => Promise.all(ids.map(id => Promise.resolve(idFetcher(id, context, info)))),
};
return { nodeInterface, nodeField, nodesField };
}
Then:
import { nodeDefinitions } from './node';
const { nodeField, nodesField, nodeInterface } = nodeDefinitions(
// A method that maps from a global id to an object
async (globalId, context) => {
const { id, type } = fromGlobalId(globalId);
if (type === 'User') {
return UserLoader.load(context, id);
}
....
...
...
// it should not get here
return null;
},
// A method that maps from an object to a type
obj => {
if (obj instanceof User) {
return UserType;
}
....
....
// it should not get here
return null;
},
);
The load method resolves the actual object. This part you would have work more specifically with your DB and etc...
If it's not clear, you can ask! Hope it helps :)