How to pass Model sequelize as parameter? - node.js

i try to pass a model class as parameter, but typescript doesn't see methods, properties.
import type { Model } from 'sequelize';
const isExistsValidator = (model: typeof Model, field: string) => {
return async (value): boolean => {
const result = await model.findOne({ where: { [field]: value } }); // No overload matches this call.
// ...
};
};
I have tried some variants:
import type { Model, ModelStatic } from 'sequelize';
const isExistsValidator = <M extends Model>(model: ModelStatic<M>, field: string) {
// ...
const result = await model.findOne({ where: { [field]: value } });// Property 'findOne' does not exist on type
}
import type { Model } from 'sequelize';
const isExistsValidator = (model: Model, field: string) {
// ...
const result = await model.findOne({ where: { [field]: value } });// Property 'findOne' is a static member of type 'Model<any, any>'
How it will be use (something like this):
const isExistsName = isExistsValidator(UserModel, 'name');
// ...
if(await isExistsName('alex4answer')) {
throw new Error('Name already in use');
}
What am i doing wrong?
i'm new in typescript

Related

Can't query database using the generated models from sequelize-auto

I followed the instructions on how to generate models from an existing database as pointed here https://github.com/sequelize/sequelize-auto. However when I try to query the database using the model i get this error:
TypeError: Cannot convert undefined or null to object
at Function.keys ()
at Function.findAll (/home/jefry/node/sequelize/node_modules/sequelize/src/model.js:1755:47)
at /home/jefry/node/sequelize/src/components/users/controllers.ts:11:32
at Generator.next ()
at /home/jefry/node/sequelize/src/components/users/controllers.ts:31:71
at new Promise ()
at __awaiter (/home/jefry/node/sequelize/src/components/users/controllers.ts:27:12)
at list (/home/jefry/node/sequelize/src/components/users/controllers.ts:45:12)
at Layer.handle [as handle_request] (/home/jefry/node/sequelize/node_modules/express/lib/router/layer.js:95:5)
at next (/home/jefry/node/sequelize/node_modules/express/lib/router/route.js:144:13)
Controller*
export async function list(req: Request, res: Response) {
try {
const list = await primera.findAll();
res.json({ list });
} catch (error) {
console.log(error);
}
}
Model*
import * as Sequelize from 'sequelize';
import { DataTypes, Model, Optional } from 'sequelize';
export interface primeraAttributes {
verdad?: number;
}
export type primeraOptionalAttributes = "verdad";
export type primeraCreationAttributes = Optional<primeraAttributes, primeraOptionalAttributes>;
export class primera extends Model<primeraAttributes, primeraCreationAttributes> implements primeraAttributes {
verdad?: number;
static initModel(sequelize: Sequelize.Sequelize): typeof primera {
return primera.init({
verdad: {
type: DataTypes.BOOLEAN,
allowNull: true
}
}, {
sequelize,
tableName: 'primera',
timestamps: false
});
}
}
init-models
import type { Sequelize } from "sequelize";
import { primera as _primera } from "./primera";
import type { primeraAttributes, primeraCreationAttributes } from "./primera";
export {
_primera as primera,
};
export type {
primeraAttributes,
primeraCreationAttributes,
};
export function initModels(sequelize: Sequelize) {
const primera = _primera.initModel(sequelize);
return {
primera: primera,
};
}
You should pass at least an empty object as an argument of findAll:
const list = await primera.findAll({});

How do I propagate schema types to different files on fastify?

I am trying out Fastify with Typescript and I would like to have separation of concerns. Specifically, I want to separate my schema from my controller and routers. However, I do not manage to pass around the schema types easily.
My server creation is as follows:
import Fastify from 'fastify';
import { JsonSchemaToTsProvider } from '#fastify/type-provider-json-schema-to-ts';
import balanceRoute from './features/balance/route';
const createServer = () => {
const server = Fastify({ logger: true }).withTypeProvider<JsonSchemaToTsProvider>();
server.get('/healthz', async (request, reply) => {
return reply.code(200).send({
data: {
status: 'OK'
}
});
})
server.register(balanceRoute, { prefix: '/balance' });
return server;
}
My route is:
const route = async (server: FastifyTyped) => {
server.get(
'/:address',
{
schema: GetBalanceSchema
},
getBalanceController
);
};
My controller is:
export const getBalanceController = async (req: FastifyRequest, res: FastifyReply) => {
console.log('Within get balance handler');
const address = req.params.address; // PROBLEM IS HERE
const currentBalance = await getBalance('', '');
res.send({ hello: 'hello' });
};
My schema is as follows:
import { FastifySchema } from 'fastify';
export const GetBalanceSchema: FastifySchema = {
params: {
address: { type: 'string' }
},
querystring: {
chainID: { type: 'string' }
},
response: {
200: {
type: 'object',
properties: {
data: {
type: 'string'
}
}
}
}
} as const;
In the controller code, I cannot get Typescript to infer that req.params has an address field. Also, if I move the controller within the route it does not help neither.
Any clue about how to get this working in an easy way?
Thank you in advance and regards
That's because you've given your schema an explicit type annotation FastifySchema, which overrides the as const. You can try removing the explicit type annotation:
export const GetBalanceSchema = {
...
} as const;
Or not using as const:
export const GetBalanceSchema: FastifySchema = {
...
};
Maybe even using a utility function to enforce the type while retaining the original structure of the object:
function schema<S extends FastifySchema>(schema: S): S { return schema; }
export const GetBalanceSchema = schema({
...
});
But in TypeScript 4.9, we've got a new satisfies operator, that we can use like this:
export const GetBalanceSchema = {
...
} satisfies FastifySchema;

Is there a better way to define these funtions?

There has to be a better way of doing this.
I feel like I'm repeating myself.
Can anyone help? I'm a bit new to this. Is there some way that these different data functions and variables into a class that I can inherit from?
Thanks in advance!
import { Guild, GuildMember, TextChannel } from "discord.js"
import mafiaRoleSchema from "./models/mafiaRole-schema"
import willSchema from "./models/will-schema"
interface data<type>{
// MemberID: message
[key: string]: type
}
let willData = {} as data<string>
export async function setWillData(key : GuildMember, value: string) {
willData[key.id] = value
await willSchema.findOneAndUpdate({
_id: key.id
}, {
_id: key.id,
value
}, {
upsert: true
})
}
export async function getWillData(key : GuildMember): Promise<string | null>{
let data = willData[key.id]
if(!data){
const results = await willSchema.findById(key.id)
if (!results){
return null
}
const {text} = results
data = willData[key.id] = text
}
return data
}
let mafiaRoleData = {} as data<string>
export async function setmafiaRoleData(key : GuildMember, value: string) {
mafiaRoleData[key.id] = value
await mafiaRoleSchema.findOneAndUpdate({
_id: key.id
}, {
_id: key.id,
value
}, {
upsert: true
})
}
export async function getmafiaRoleData(key : GuildMember): Promise<string | null>{
let data = mafiaRoleData[key.id]
if(!data){
const results = await mafiaRoleSchema.findById(key.id)
if (!results){
return null
}
const {text} = results
data = mafiaRoleData[key.id] = text
}
return data
}
let welcomeData = {} as data<[TextChannel, string]>
export async function setwelcomeData(key : Guild, value: [TextChannel, string]) {
welcomeData[key.id] = value
const [target, text] = value
await mafiaRoleSchema.findOneAndUpdate({
_id: key.id
}, {
_id: key.id,
text,
channelId: target.id
}, {
upsert: true
})
}
export async function getwelcomeData(key : Guild): Promise<[TextChannel, string] | null>{
let data = welcomeData[key.id]
if(!data){
const results = await mafiaRoleSchema.findById(key.id)
if (!results){
return null
}
const {channelId, text} = results
const channel = key.channels.cache.get(channelId) as TextChannel
data = [channel, text]
}
return data
}
I've tried putting the functions into the interface. But that didn't work. I honestly don't know where to go.
I know it seems like a lot, but this isn't as bad as you think. Your welcome message code is sufficiently different than your other code, which merits it having its own functions.
As for the mafia and will data, you could do an abstraction kind of like this:
import mafiaRoleSchema from "./models/mafiaRole-schema"
import willSchema from "./models/will-schema"
interface Data<type>{
// MemberID: message
[key: string]: type
}
type Schema = typeof mafiaRoleSchema | typeof willSchema // this could be more generic if you have access to those types
class SchemaController {
private schema: Schema
private data: Data<string>
constructor(schema: Schema) {
this.schema = schema
this.data = {}
}
async setData(key: GuildMember, value: string) {
this.data[key.id] = value
await this.schema.findOneAndUpdate({
_id: key.id
}, {
_id: key.id,
value
}, {
upsert: true
})
}
async getData(key : GuildMember): Promise<string | null> {
let data = this.data[key.id]
if(!data){
const results = await this.schema.findById(key.id)
if (!results){
return null
}
const {text} = results
data = this.data[key.id] = text
}
return data
}
const mafiaController = new SchemaController(mafiaRoleSchema)
const willController = new SchemaController(willSchema)

How to extend mongoose Query class with Typescript?

I'm trying to implement caching with Mongoose, Redis, and Typescript. My cache.ts file :
import mongoose, { model, Query } from "mongoose";
import redis from "redis";
//import { CacheOptions } from "../../types/mongoose";
type CacheOptions = { key?: string };
const client = redis.createClient();
const getCache = function (
hashKey: string,
key: string
): Promise<string | null> {
return new Promise((res, rej) => {
client.hget(hashKey, key, (err, val) => {
if (err) rej(err);
else res(val);
});
});
};
const exec = mongoose.Query.prototype.exec;
mongoose.Query.prototype.cache = function (options: CacheOptions = {}) {
this.useCache = true;
this.hashKey = JSON.stringify(options.key || "");
return this; //make cache() chainable
};
mongoose.Query.prototype.exec = async function () {
if (!this.useCache) {
//NO CACHE
return exec.apply(this);
}
const key = JSON.stringify({
...this.getQuery(),
collection: this.model.collection.name,
});
const cacheValue = await getCache(this.hashKey, key);
if (cacheValue) {
console.log("DATA FROM CACHE");
const doc = JSON.parse(cacheValue);
//convert plain object to mongoose object
return Array.isArray(doc)
? doc.map((d) => new this.model(d))
: new this.model(doc);
}
const result = await exec.apply(this);
client.hset(this.hashKey, key, JSON.stringify(result));
return result;
};
/**
*
* #param hashKey hashkey to remove
*/
const clearHash = (hashKey: string) => {
client.del(JSON.stringify(hashKey));
};
export { clearHash };
And this is my type declaration file under types folder: mongoose.d.ts
declare module "mongoose" {
export interface Query<
ResultType,
DocType extends Document,
THelpers = {}
> {
cache(): Query<T>;
useCache: boolean;
hashKey: string;
model: Model<T>;
}
}
VsCode IntelliSense doesn't give any warning or error. When I run the code I get following error:
TSError: тип Unable to compile TypeScript:
src/services/product/product.controller.ts:92:67 - error TS2551: Property 'cache' does not exist on type 'Query<IProduct | null, IProduct, {}>'. Did you mean 'catch'?
92 const foundProduct = await Product.findOne({ slug }, { __v: 0 }).cache();
I'm not sure if I correctly defined the types but it seems like TypeScript doesn't see my declaration or something else. If you have any suggestion I'll be appreciate.
one alternative option is that you can do is override that Query class in index.d.ts by adding cache: any
Here's how I solved this issue. In the same cache.ts file do this,
declare module 'mongoose' {
interface DocumentQuery<
T,
DocType extends import('mongoose').Document,
QueryHelpers = {}
> {
mongooseCollection: {
name: any;
};
cache(): DocumentQuery<T[], Document> & QueryHelpers;
useCache: boolean;
hashKey: string;
}
interface Query<ResultType, DocType, THelpers = {}, RawDocType = DocType>
extends DocumentQuery<any, any> {}
}
then,
mongoose.Query.prototype.cache = function (options: Options = {}) {
this.__cache = true;
this.__hashKey = JSON.stringify(options.key || '');
// To make it chain-able with the queries
// Ex: Blog
// .find()
// .cache()
// .limit(10)
return this;
};
Because I was not fully confident if the DocumentQuery<any, any> {} part is exactly correct so I didn't create it on a separate mongoose.d.ts file.
But this code will definitely work.

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.

Resources