How can I subclass Model and properly add a static method? - node.js

I'm learning how to use Sequelize as the ORM for a small web app. Each record in the (MySQL) database has a uuid field. I want to add a findByUuid static method to all of the models. Here's my attempt:
class Base extends Model {
static findByUuid = async (uuid) => {
return await super.findOne({ where: {uuid: uuid} });
}
}
class List extends Base {
}
List.init({
uuid: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: Sequelize.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
nodelName: 'List',
indexes: [
{ fields: ['name'] },
{ fields: ['uuid'] }
]
});
(async () => {
await sequelize.sync({ force: true });
const list = List.build({name: 'New List'});
await list.save();
console.log('id: ' + list.id);
console.log('uuid: ' + list.uuid);
const direct_retrieved = await List.findOne({ where: {uuid: list.uuid} });
console.log('direct name: ' + direct_retrieved.name);
const retrieved = await List.findByUuid(list.uuid);
console.log('static retrieved: ' + retrieved.name);
})();
And here's the output (excluding the echoed SQL commands):
id: 1
uuid: 371971dd-a4d9-43aa-ac10-afe3c10154b0
direct name: New List
/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1692
this.warnOnInvalidOptions(options, Object.keys(this.rawAttributes));
^
TypeError: Cannot convert undefined or null to object
at Function.keys (<anonymous>)
at Function.findAll (/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1692:47)
at Function.findOne (/Users/chuck/Projects/app/node_modules/sequelize/lib/model.js:1917:23)
at Function.findByUuid (/Users/chuck/Projects/app/models/models.js:7:36)
at /Users/chuck/Projects/app/models/models.js:181:34
at processTicksAndRejections (node:internal/process/task_queues:93:5)
So directly working with findOne is working, but trying to do so within the static method causes the error, and I have not been able to figure out why.
I think the problem is that the Base class has no knowledge of a uuid field. If I put the static method into the List class, it works properly. So the only solution I have so far is to repeat that code for every actual table since I don't (yet) see a way to define the uuid field in the Base class (which would be preferable anyway.

It turns out that I needed to override init to let Base know about the uuid field. Here's the working solution.
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = require('./helpers/sequelize');
class Base extends Model {
static findByUuid = async (uuid) => {
return await super.findOne({ where: {uuid: uuid} });
}
static init = (attributes, options = {}) => {
attributes.uuid = {
type: DataTypes.UUID,
allowNull: false,
defaultValue: Sequelize.UUIDV4
};
options.indexes.push({ fields: ['uuid'] });
super.init(attributes, options);
}
}
class List extends Base {
}
List.init({
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'List',
indexes: [
{ fields: ['name'] }
]
});
(async () => {
await sequelize.sync({ force: true });
const list = List.build({name: 'New List'});
await list.save();
console.log('id: ' + list.id);
console.log('uuid: ' + list.uuid);
const direct_retrieved = await List.findOne({ where: {uuid: list.uuid} });
console.log('direct: ' + direct_retrieved.name);
const retrieved = await List.findByUuid(list.uuid);
console.log('static: ' + retrieved.name);
})();

Related

How to stub query helper method Mongoose?

I'm using Sinon to test my Express/Typescript application. In Mongoose I have models with query helper methods to make query chains.
I have this schema
export const articuloSchema = new Schema({
_id: { type: String, default: v4 },
numeroInventario: { type: String, required: true, unique: true },
descripcion: { type: String, trim: true },
}, {
versionKey: false,
query: {
paginate(page: number, limit: number) {
return this.skip(page - 1).limit(limit);
}
}
});
export type ArticuloType = InferSchemaType<typeof articuloSchema>;
export const Articulo = mongoose.model('Articulo', articuloSchema);
and I want to fake my query helper method with Sinon like this
const getMultipleArticulos (): ArticuloType[] => {
const arr:ArticuloType = []
for (let i = 0; i < 5; i++) {
arr.push({
numeroInventario: i,
descripcion: 'string'
})
}
return arr;
}
it('Should return a list of items that belong to a user', function (done) {
const paginateFake = sinon.fake.resolves(getMultipleArticulos());
sinon.replace(Articulo, 'paginate', paginateFake);
chai.request(app)
.get(`/api/v1/users/${userId}/articulos`)
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
// more expects
done();
});
});
The problem is that I cannot stub those methods, it says that it can't stub non existent property of 'paginate' (which is the query helper method I added to my model).

When there is the following entity, how should I write find where nestjs typeorm?

I want to use the typeorm to find the desired value in the technical -> mapping table <-item relationship.
However, the error "Unknown column 'tnItem.itemId' in 'where cause' occurs.
I know that an error occurs because it is an item rather than an itemId in tnItem, but I don't know how to write a query.
my entity
#Entity("tn_item")
export class TechnicianItem extends BaseModel {
#ManyToOne(() => Technician, (technician) => technician.tnItem, {
primary: true,
lazy: true
})
#JoinColumn([{ name: "tn_id", referencedColumnName: "tnId" }])
technician: Technician;
#ManyToOne(() => Item, (item) => item.tnItem, {
primary: true,
lazy: true
})
#JoinColumn([{ name: "item_id", referencedColumnName: "itemId" }])
item: Item;
#Column("int", { nullable: false, name: "period" })
period!: number;
}
my method
async findAllTechnician(searchDto: SearchDto): Promise<Technician[]> {
const findTechnician = await this.find({
relations: ["tnItem", "tnConcept"],
where: (qb) => {
qb.where(`tnItem.itemId IN (${searchDto.items})`).andWhere(
`tnConcept.conceptId IN (${searchDto.concepts})`
);
}
});
return findTechnician;
}
I was solved using the createQueryBuilder.
async findAllTechnician(searchDto: SearchDto): Promise<Technician[]> {
const findTechnician = await this.createQueryBuilder("tn")
.innerJoinAndSelect(TechnicianItem, "tni", "tni.tnId = tn.tnId")
.innerJoinAndSelect(TechnicianConcept, "tnc", "tnc.tnId = tn.tnId")
.where("tni.itemId IN (:items)", { items: searchDto.items })
.andWhere("tnc.conceptId IN (:concepts)", {
concepts: searchDto.concepts
})
.getMany();
return findTechnician;
}

Passing sequelize model instance to a service is not working in express.js

I want to access findById function of CRUDService in ItemService. I'm getting response from readAll function but not getting from findById. I think dao object what I'm passing to CRUDService from ItemService through constructor is not working. I'm new in node js and express js. Could you help me please.
This is Crud Service
class CRUDService{
constructor(dao) {
this.dao = dao;
}
readAll = () => {
const rows = dao.findAll();
return rows;
};
findById = (rowId) => {
const row = dao.findByPk(rowId);
return row;
};
}
module.exports = CRUDService
This is Item Service
const CRUDService = require('../common/crud.service.js');
const ItemDAO = require('./item.dao.js');
class ItemService extends CRUDService{
constructor() {
const dao = new ItemDAO();
super(ItemDAO);
}
readAll = () => {
const rows = ItemDAO.findAll();
return rows;
};
}
module.exports = ItemService
This is DAO
const {Sequelize, Model} = require('sequelize');
const sequelize = require('../database');
class ItemDAO extends Model {}
ItemDAO.init(
{
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV1
},
name_en: Sequelize.STRING,
name_local: Sequelize.STRING,
created_at: Sequelize.TIME,
created_by: Sequelize.STRING,
is_deleted: Sequelize.BOOLEAN
},
{
sequelize,
modelName: 'Item',
schema: 'cat',
timestamps: false,
tableName: 'item'
}
);
module.exports = ItemDAO;
You need to pass the instance of your ItemDAO to the super constructor.
const CRUDService = require('../common/crud.service.js');
const ItemDAO = require('./item.dao.js');
class ItemService extends CRUDService{
constructor() {
super(new ItemDAO()); // ---> here
}
readAll = () => {
const rows = this.readAll();
return rows;
};
}
module.exports = ItemService
Also need to modify your service.
class CRUDService{
constructor(dao) {
this.dao = dao;
}
readAll = () => this.dao.findAll().then(rows => rows);
findById = (rowId) => this.dao.findByPk(rowId).then(row => row);
}
Also remember those methods return promises so better to use .then() or use async/await.

Typescript Field is not assignable to type 'never' what am i missing to make it a date type?

I am creating my user schema and I get the following error when trying to assign a value to the registrationStatusTimeStamps field on the function #pre<save> and ts keeps saying that is of type never.
I've tried about everything but i keep having TS complaining and the field not saving.
I guess my desired output is evident save fields in an object with a time time
What am i missing ?
error TS2322: Type 'Date' is not assignable to type 'never'
Desired Stored output
{
registrationStatusTimeStamps: {
initial: 2018-10-18 14:29:07.000,
personalDetails: 2018-10-18 14:29:07.000,
selectBroker: 2018-10-18 14:29:07.000,
appropriatenessQuestionnaire: 2018-10-18 14:29:07.000,
documentCheck: 2018-10-18 14:29:07.000,
completed: 2018-10-18 14:29:07.000
}
}
The code
export enum RegistrationStatuses {
initial = "initial",
personalDetails = "personalDetails",
selectBroker = "selectBroker",
appropriatenessQuestionnaire = "appropriatenessQuestionnaire",
documentCheck = "documentCheck",
completed = "completed"
};
#pre<User>("save", function (next: HookNextFunction) {
// Update registration timestamps
if (this.isModified("registrationStatus")) {
this.registrationStatusTimeStamps[this.registrationStatus] = new Date();
}
return next();
})
export class User extends Typegoose {
#prop({
required: true,
default: RegistrationStatuses.initial
})
public registrationStatus: RegistrationStatuses;
#prop({
default: {},
_id: false,
})
public registrationStatusTimeStamps: { [k in RegistrationStatuses]?: Date };
}
I dont know which version of typegoose you were using, but if it is 6.1.5, then the following would work
(6.0.0 got a major type overhaul)
// NodeJS: 12.13.0
// MongoDB: 4.2-bionic (Docker)
import { getModelForClass, pre, prop } from "#typegoose/typegoose"; // #typegoose/typegoose#6.1.5
import * as assert from "assert"; // node's assert
import * as mongoose from "mongoose"; // mongoose#5.7.12
export enum RegistrationStatuses {
initial = "initial",
personalDetails = "personalDetails",
selectBroker = "selectBroker",
appropriatenessQuestionnaire = "appropriatenessQuestionnaire",
documentCheck = "documentCheck",
completed = "completed"
}
#pre<User>("save", function () {
// Update registration timestamps
if (this.isModified("registrationStatus") || this.isNew) {
this.registrationStatusTimeStamps[this.registrationStatus] = new Date();
}
})
export class User {
#prop({ required: true, default: RegistrationStatuses.initial })
public registrationStatus!: RegistrationStatuses;
#prop({
default: {},
_id: false
})
public registrationStatusTimeStamps!: { [k in RegistrationStatuses]?: Date };
}
const UserModel = getModelForClass(User);
(async () => {
await mongoose.connect(`mongodb://localhost:27017/`, { useNewUrlParser: true, dbName: "verifyStackOverflow1", useCreateIndex: true, useUnifiedTopology: true });
const user = new UserModel({});
console.log("before save", user, user.isModified("registrationStatus"), user.isNew); // before save { registrationStatus: 'initial', _id: <SomeObjectId> } false true
assert(user.registrationStatus === RegistrationStatuses.initial);
assert(Object.keys(user.registrationStatusTimeStamps).length === 0);
await user.save();
console.log("after save", user); // after save { registrationStatus: 'initial', _id: <SomeObjectId>, registrationStatusTimeStamps: { initial: 2019-11-30T18:27:02.507Z }, __v: 0 }
assert(user.registrationStatus === RegistrationStatuses.initial);
assert(typeof user.registrationStatusTimeStamps === "object");
assert(user.registrationStatusTimeStamps[RegistrationStatuses.initial] instanceof Date);
await mongoose.disconnect(); // to not have it running infinitly
})();

How to execute a mutation in GraphQL?

In GraphQL we have basically two types of operations: queries and mutations. While queries are well described in the documentation and there are many examples of them, I'm having a hard time to understand how to execute a mutation. Mutations obviously are update methods.
I've created very simple Node.js server:
var express = require("express");
var graphqlHTTP = require("express-graphql");
var graphql = require("graphql");
var inMemoryDatabase = require("./inMemoryDatabase").inMemoryDatabase;
var _ = require("lodash-node");
var userType = new graphql.GraphQLObjectType({
name: "User",
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
}
});
var queryType = new graphql.GraphQLObjectType({
name: "Query",
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: function(parent, { id }) {
return _.find(inMemoryDatabase, { id: id });
}
}
}
});
var mutationType = new graphql.GraphQLObjectType({
name: "Mutation",
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
},
resolve: function(parent, { id, name }) {
var index = _.findIndex(inMemoryDatabase, { id: id });
inMemoryDatabase.splice(index, 1, { id: id, name: name });
return _.find(inMemoryDatabase, { id: id });
}
}
}
});
var schema = new graphql.GraphQLSchema({
query: queryType,
mutation: mutationType
});
var app = express();
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
graphiql: true
})
);
var port = 9000;
if (process.env.PORT) {
port = process.env.PORT;
}
app.listen(port);
console.log("Running a GraphQL API server at localhost:" + port + "/graphql");
In memory database is just in an array of User objects {id, name}:
var inMemoryDatabase = [
{
id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8",
name: "Mark"
},
{
id: "2fb6fd09-2697-43e2-9404-68c2f1ffbf1b",
name: "Bill"
}
];
module.exports = {
inMemoryDatabase
};
Executing query to get user by id looks as follows:
{
user(id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8"){
name
}
}
How would the mutation changing user name look like?
Hey may completely be missing what you are saying, but the way that I look at a mutation is like this
I get some arguments and a field, that is the same thing as params and a path in rest, with those i do something (in your case lookup the user and update the attribute based on the arguments passed in
After That, i return something from the resolve function that will fulfill the type you specify in the type of the mutation
var mutationType = new graphql.GraphQLObjectType({
name: "Mutation",
fields: {
user: {
// You must return something from your resolve function
// that will fulfill userType requirements
type: userType,
// with these arguments, find the user and update them
args: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
},
// this does the lookup and change of the data
// the last step of your result is to return something
// that will fulfill the userType interface
resolve: function(parent, { id, name }) {
// Find the user, Update it
// return something that will respond to id and name, probably a user object
}
}
}
});
Then with that as a context, you pass some arguments and request back a user
mutation updateUser {
user(id: "1", name: "NewName") {
id
name
}
}
In a normal production schema you would also normally have something like errors that could be returned to convey the different states of the update for failed/not found
#Austio's answer was pretty close, but the proper way is:
mutation updateUser {
user(id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8", name: "Markus") {
id
name
}
}
if we connect directly with MongoDB below will help you.
mutation {
taskTrackerCreateOne
(
record:
{
id:"63980ae0f019789eeea0cd33",
name:"63980c86f019789eeea0cda0"
}
)
{
recordId
}
}

Resources