Sequelize Typescript create object with associations - node.js

I'm trying to build a new instance with OneToOne association, according to https://www.npmjs.com/package/sequelize-typescript
models/measure.ts :
#Table({ tableName: "measure" })
export class Measure extends Model {
...
// Token
#ForeignKey(() => Token)
#Column({ type: DataType.INTEGER, allowNull: false, unique: true })
id_token!: number
#BelongsTo(() => Token)
token!: Token
}
models/token.ts :
#Table({ tableName: "token" })
export class Token extends Model {
...
// Measure
#HasOne(() => Measure)
measure: Measure
}
my sequelize instance :
export const sequelize: Sequelize = new Sequelize({
...
repositoryMode: true,
})
sequelize.addModels([Token, Measure])
export const tokenRepository = sequelize.getRepository(Token)
export const measureRepository = sequelize.getRepository(Measure)
now i want to create a new Measure instance, with an existing Token :
const token = await tokenRepository.findOne({where: {value: value}})
...
console.log(token.isNewRecord) // false
const measure = measureRepository.build({...params, token: token}, {include: [{model: tokenRepository}]})
console.log(measure.token.isNewRecord) // true
await measure.save() // it try to insert an new token here
Sequelize try to insert a new token instead of insert only the measure. Where is my mistake ?

I faced this issue some times ago and by reading the docs and trying few things on my side I conclude it was not possible.
From what I saw you can create a new object and all is nested objects in one time. But you can't create a new object and link it to other existing objects in one time.
To do so you will have to do yourself the necessary find and/or update.
I never managed to make it work in one step and the documentation never talk about this. Always says that all elements are new.
An instance can be created with nested association in one step, provided all elements are new.
For more details :
https://sequelize.org/master/manual/creating-with-associations.html

Related

How to search a key value pair inside a json object using typeorm

I am using typeorm to manage my postgresql database in nestjs. I had to save a json response in the db, so I saved whole response under one column. This is my entity.
/* eslint-disable prettier/prettier */
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
#Entity({name:'travel_bookings'})
export class TravelBookings {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column('uuid')
userId:string;
#Column({ type: 'json' })
booking_response: string;
#Column({ nullable: true })
Status:string;
#CreateDateColumn({ name: 'created_at' }) 'created_at': Date;
#UpdateDateColumn({ name: 'updated_at' }) 'updated_at': Date;
}
`
The booking_response is a long json data, in which there is one id. Now I am creating a function to update Status where the id inside the json matches with the id I provide.
This is one example json response -
{"type":"flight-order","id":"eJzTd9f397V09w8FAAs4AmY%3D","queuingOfficeId":"NCE4D31SB","associatedRecords":[{"reference":"OM9GOU","creationDate":"2023-02-07T11:40:00.000","originSystemCode":"GDS","flightOfferId":"1"}],"flightOffers":[{"type":"flight-offer","id":"1","source":"GDS","nonHomogeneous":false,"lastTicketingDate":"2023-02-08","itineraries":[{"segments":[{"departure":{"iataCode":"BOS","terminal":"C","at":"2023-03-10T22:55:00"},"arrival":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T10:20:00"},"carrierCode":"TP","number":"216","aircraft":{"code":"32Q"},"duration":"PT6H25M","id":"9","numberOfStops":0,"co2Emissions":[{"weight":303,"weightUnit":"KG","cabin":"ECONOMY"}]},{"departure":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T11:45:00"},"arrival":{"iataCode":"MAD","terminal":"2","at":"2023-03-11T14:05:00"},"carrierCode":"TP","number":"1014","aircraft":{"code":"32Q"},"duration":"PT1H20M","id":"10","numberOfStops":0,"co2Emissions":[{"weight":68,"weightUnit":"KG","cabin":"ECONOMY"}]}]},{"segments":[{"departure":{"iataCode":"MAD","terminal":"2","at":"2023-03-11T21:10:00"},"arrival":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T21:30:00"},"carrierCode":"TP","number":"1019","aircraft":{"code":"321"},"duration":"PT1H20M","id":"83","numberOfStops":0,"co2Emissions":[{"weight":68,"weightUnit":"KG","cabin":"ECONOMY"}]},{"departure":{"iataCode":"LIS","terminal":"1","at":"2023-03-12T11:40:00"},"arrival":{"iataCode":"BOS","terminal":"E","at":"2023-03-12T15:20:00"},"carrierCode":"TP","number":"217","aircraft":{"code":"32Q"},"duration":"PT7H40M","id":"84","numberOfStops":0,"co2Emissions":[{"weight":303,"weightUnit":"KG","cabin":"ECONOMY"}]}]}],"price":{"currency":"USD","total":"613.85","base":"184.00","fees":[{"amount":"0.00","type":"TICKETING"},{"amount":"0.00","type":"SUPPLIER"},{"amount":"0.00","type":"FORM_OF_PAYMENT"}],"grandTotal":"613.85","billingCurrency":"USD"},"pricingOptions":{"fareType":["PUBLISHED"],"includedCheckedBagsOnly":false},"validatingAirlineCodes":["TP"],"travelerPricings":[{"travelerId":"1","fareOption":"STANDARD","travelerType":"ADULT","price":{"currency":"USD","total":"613.85","base":"184.00","taxes":[{"amount":"5.60","code":"AY"},{"amount":"4.40","code":"J9"},{"amount":"15.80","code":"JD"},{"amount":"0.70","code":"OG"},{"amount":"16.10","code":"PT"},{"amount":"3.60","code":"QV"},{"amount":"42.20","code":"US"},{"amount":"3.83","code":"XA"},{"amount":"4.50","code":"XF"},{"amount":"7.00","code":"XY"},{"amount":"6.52","code":"YC"},{"amount":"27.60","code":"YP"},{"amount":"292.00","code":"YQ"}],"refundableTaxes":"86.75"},"fareDetailsBySegment":[{"segmentId":"9","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"10","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"83","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"84","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}}]}]}],"travelers":[{"id":"1","dateOfBirth":"1982-01-16","gender":"MALE","name":{"firstName":"JORGE","lastName":"GONZALES"},"documents":[{"number":"00000000","issuanceDate":"2015-04-14","expiryDate":"2025-04-14","issuanceCountry":"ES","issuanceLocation":"Madrid","nationality":"ES","birthPlace":"Madrid","documentType":"PASSPORT","holder":true}],"contact":{"purpose":"STANDARD","phones":[{"deviceType":"MOBILE","countryCallingCode":"34","number":"480080076"}],"emailAddress":"jorge.gonzales833#telefonica.es"}}],"remarks":{"general":[{"subType":"GENERAL_MISCELLANEOUS","text":"ONLINE BOOKING FROM INCREIBLE VIAJES"}]},"ticketingAgreement":{"option":"DELAY_TO_CANCEL","delay":"6D"},"automatedProcess":[{"code":"IMMEDIATE","queue":{"number":"0","category":"0"},"officeId":"NCE4D31SB"}],"contacts":[{"addresseeName":{"firstName":"PABLO RODRIGUEZ"},"address":{"lines":["Calle Prado, 16"],"postalCode":"28014","countryCode":"ES","cityName":"Madrid"},"purpose":"STANDARD","phones":[{"deviceType":"LANDLINE","countryCallingCode":"34","number":"480080071"},{"deviceType":"MOBILE","countryCallingCode":"33","number":"480080072"}],"companyName":"INCREIBLE VIAJES","emailAddress":"support#increibleviajes.es"}]}
This is my function -
async flightCancel(data) {
var amadeus = await new Amadeus({
clientId: process.env.API_KEY,
clientSecret: process.env.API_SECRET
});
// const output=await this.travelBookingsRepo.findOne({where:{ booking_response:"eJzTd9f397V09w8FAAs4AmY%3D"}})
// console.log(output);
this.travelBookingsRepo.update({ booking_response: { id: data } },{Status:'Cancelled'})
return amadeus.booking.flightOrder(data).delete();
}
I found some solutions on web using entityManager , getManager/getConnection but these are deprecated and work no more. If issue is about update function, then I have tried it with findOne and save methods too but still unsuccessful. Please help me in resolving my issue.
If you are using after typeorm#0.3.0, you can not use entityManager , getManager/getConnection.
Instead you could use 'DataSource'
You could check my github.

Reference Mongodb schema from other microservices

I currently have a microservice architecture powered by Apollo Federation, where a service has its own database. Consider this example, the user's service has its own database, the posts service has its own database, and the comments service has its own database. Currently, each schema sits in its own service, but each of these models references each other. For example
// posts.model
const PostSchema = new Schema({
title: String,
name: String,
user: { type: ObjectId, ref: 'User' }
});
In this case, a Post has the corresponding user attached to it. I am using typegoose alongside type-graphql in another microservice while the rest microservices are written in plain javascript. Now in my typegoose class, I am trying to achieve the same thing above. I have a class (model) that is referencing another entity in a different service/database. How do I represent it in my typegoose model? E.g
export class Post {
#GqlField(() => String, { nullable: true })
#prop()
title: string;
#GqlField(() => User)
#prop({ ref: () => User, required: true })
user: Ref<User>; // this is meant to reference user in the user-service.
#GqlField(() => String)
#prop({required: true,})
name: string;
}
Do I use an abstract class to represent the User model, if yes, how do I go about this? Or is there another method that I can use to achieve this? I tried creating a User model class in the current microservice, but that user's model is going to be registered on the current database connection and I don't want that.
I'll appreciate any advice, thank you in advance.
What you want to do is called "Cross-Database Population", where mongoose has a documentation page for it, where the TL;DR; basically is:
Either pass a Model instance to ref (which currently is not supported by typegoose) or use the model option in a populate call.
Example:
(you still require the Schema from the other services and a database connection to those databases)
#modelOptions({ existingConnection: UserDBConnection }) // this can also be set in the call for "getModelForClass"
class User {
#prop()
public name?: string;
}
const UserModel = getModelForClass(User);
// or if setting the connection here
const UserModel = getModelForClass(User, { existingConnection: UserDBConnection });
// without setting a explicit database, "mongoose.connection" will be used (which is the default connection in mongoose)
class Post {
#prop({ ref: () => User })
public user?: Ref<User>;
}
const PostModel = getModelForClass(PostModel);
// somewhere later in your code
await postdoc.populate({ path: 'user', model: UserModel }).execPopulate();

Nodejs and Sequelize Limitations regarding creating dynamic models

I've a simple question related to my scenario. I've a react frontend where admin have a form to define it's own sign up form for the users.
Admin will send a set of fields with it's data types
Backend NodeJs will take those fields and run some script/function to create table in a database with the data types accordingly
It also needs to create a model for the respective table dynamically
I'm just finding a way to get it done. Any alternate solution is welcomed or any suggestions to refine my scenario?
Thanks in advance
You can just map all indicated fields into field definitions for Sequielize like:
fieldName: {
field: 'field_name',
type: DataTypes.TEXT,
allowNull: false
},
and call sequelize.define which will return you a Sequilize model that you can use to execute queries:
// NOTE "sequelize" should be an instance of Sequelize, i.e. an existing connection.
function registerModel(tableName, modelName, fields) {
const model = sequelize.define(modelName, {
...fields
}, {
tableName: tableName,
timestamps: false,
});
return model;
}
Register a new model:
const fields = [{
fieldName: {
field: 'field_name',
type: DataTypes.TEXT,
allowNull: false
},
}]
const newModel = registerModel('some_table', 'someModel', fields);
Use the registered model:
const items = await newModel.findAll({
where: {
// some conditions
}
})

How to run another model queries inside the sequelize hooks?

Below is my PurchaseOrder model defined in sequelize. I want to update the Supplier Model whenever there is an update to the PurchaseOrder. I thought of using the hooks to achieve this. But I couldn't able to access another model inside this model. I tried importing and all stuff, but no luck. Is this the right way to use the hooks or what should I use to achieve the same? Any help or direction is much appreciated!
module.exports = (sequelize, Sequelize) => {
const PurchaseOrder = sequelize.define("purchaseOrder", {
totalAmount: {
type: Sequelize.INTEGER
},
paid: {
type: Sequelize.BOOLEAN
},
paymentMode: {
type: Sequelize.ENUM('CASH', 'CHEQUE', 'BANK', 'CARD', 'NA')
}
}, {
freezeTableName: true,
hooks: {
beforeUpdate: (order, options) => {
// here I want to update the another model(Supplier).
// But I couldn't able to access another model inside the hook
Supplier.increment('balance'{
where: { id: order.supplierId }
});
}
}
});
return PurchaseOrder;
};
In my code I have a couple hooks that update other models (audit logging of changes for example). You need to make sure to pass along the options.transaction so that any changes are rolled back if there is an error later in the chain.
This example accesses another table keyed by other_model. When the hooks run the models should all already be registered with Sequelize.
module.exports = function Order(sequelize, DataTypes) {
const Order = sequelize.define(
'order',
{ /* columns */ },
{
hooks: {
beforeUpdate: async function(order, options) {
// get the transaction, if it is set
const { transaction } = options;
// use sequelize.models and make sure to pass the
// transaction so it is rolled back if there is an error
await sequelize.models.supplier.increment(balance, {
where: { id: order.supplierId },
transaction,
});
},
},
},
});
return Order;
}
You can try sequelize['Supplier'] because all models should be already registered in an Sequelize instance.
Nevertheless I suppose it's not a good idea to make modifications in a DB via other models in such hooks because in such cases you should take into account that all operations should be done in the same transaction i.e. should be executed as an atomic operation to avoid inconsistent state of data in a DB if some modifications fail.
Not a relatable answer, but if anyone wants to try querying a model to another model using validate custom functions. You can define your model like sequelize.models.ModelName sequelize shouldn't be imported like require('sequelize') but it should use the sequelize parameter defined in module.exports of your current model.
await sequelize.models.ModelName.findAll()

Sequelize - How to set association, save entity and return the saved entity with the associated entity

I am trying to create an association between an existing (user) entity and save the new entity (visit).
I've read the sequelize docs and can't see a better way of doing this than saving the first entity using async/await, then fetching it again passing include as an option. See below.
export const createVisit = async(req, res) => {
req.assert('BusinessId', 'Must pass businessId').notEmpty();
req.assert('UserId', 'Must pass customerId').notEmpty();
const visit = await new Visit({
UserId: req.body.UserId,
BusinessId: req.body.BusinessId,
redemption: false,
})
.save()
.catch((error) => {
res.status(400).send({ error });
});
const visitWithUser = await Visit.findById(visit.id, {include: [{model: User, attributes: ['firstName','lastName','facebook', 'gender','email']}]})
res.status(200).send({ visit: visitWithUser })
};
Is there a way to save the entity and get sequelize to return the saved entity along with any associations?
I think it supports this feature , as per the doc , you can do it like this :
Visit.create({
UserId: req.body.UserId,
BusinessId: req.body.BusinessId,
redemption: false,
}, {
include: [User]
}).then(function(comment) {
console.log(comment.user.id);
});
Here is the git discussion if you want to read.

Resources