nestjs mongoose schema with embedded objects without embedded documents - node.js

I am creating a schema that included embedded objects (not documents). I do not need embedded documents because the embedded objects do not exist independently and I do not need associated object IDs. This is not well documented in the Nest documentation but I came up with this which is working for the most part:
const pointType = {
coordinates: { type: [Number], required: true },
type: { type: ['Point'], required: true },
}
const locationType = {
name: String,
address: addressType,
geolocation: { pointType },
}
const timedLocationType = {
timestamp: { type: Date, required: true, default: Date.now() },
geolocation: { type: pointType, required: true }, // <<---- Problem is here with the 'required' attribute
}
export class TripLocation {
name: string
geolocation: Point
address?: Address
}
export class TimedLocation {
time: Date
geolocation: Point
}
#Schema()
export class Trip {
#Prop({ type: locationType, required: true })
start: TripLocation
#Prop({ type: locationType, required: true })
end: TripLocation
#Prop({ type: [timedLocationType], default: [] }) // <<-- Problem is here
route: TimedLocation[]
}
This works except for having the timedLocation array. Mongoose does not seem to like having the route property as an array. As is, the code throws this error:
.../node_modules/mongoose/lib/schema.js:1001
throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
^
TypeError: Invalid schema configuration: `True` is not a valid type at path `geolocation.required`. See .../mongoose-schematypes for a list of valid schema types.
at Schema.interpretAsType (.../node_modules/mongoose/lib/schema.js:1001:11)
If I remove the required attribute on the timedLocationType.geolocation, the error goes away. Alternatively, if I turn the route property from an array to a single object, and keep the required` attribute, the error also goes away:
#Prop({ type: timedLocationType })
route: TimedLocation

Related

'number' only refers to a type, but is being used as a value here

src/db/models/point.ts:10:11 - error TS2693: 'number' only refers to a type, but is being used as a value here.
const PointSchema: Schema = new Schema({
id: {
type: String,
required: true,
unique: true,
index: true,
},
point: {
type: number,
required: true,
},
});
export interface PointProp extends Document {
id: string;
point: number;
}
export default model<PointProp>('point', PointSchema);
You are using a TypeScript type inside an object, as value, just as what the error says. You should have given more info, but I am guessing you are working with Mongoose. In that case, number (the TypeScript type) should be Number (the object)
const PointSchema: Schema = new Schema({
id: {
type: String,
required: true,
unique: true,
index: true,
},
point: {
type: Number, // <---
required: true,
},
});
See https://mongoosejs.com/docs/guide.html#definition for more information.
It's just a typo. change number to Number
point: {
type: **Number**,
required: true,
},

Mongoose subdocuments in typescript missing Model methods

I'm using mongoose with typescript and today I stepped into some confusion with Model and Document.
Here is definition of my subdocument.
export interface IUserUnitResource {
serialNumber: string;
role: string;
name?: string;
alarms: IUserAlarmResource[];
}
export interface IUserUnitEntity extends IUserUnitResource, mongoose.Document {
alarms: mongoose.Types.DocumentArray<IUserAlarmEntity>;
}
export const userUnitSchema = new mongoose.Schema(
{
serialNumber: { type: String, required: true, index: true },
role: { type: String, default: 'user' },
name: String,
alarms: { type: [userAlarmSchema], default: [] },
},
{ _id: false }
);
In mongoose documentation they say:
An instance of a model is called a document.
So I assumed that i can call ownerDocument method on that subdocument. But typescript gives me an error:
Property 'ownerDocument' does not exist on type 'IUserUnitEntity'.
But if i try to call that method using any i get desired document.
(uu as any).ownerDocument()
Do I have the typings wrong, or it's a bug?

How to define non-required field in Mongoose schema with nested "type" property?

I have the following Mongoose schema definition in my project:
export const ProductSchema: SchemaDefinition = {
type: { type: String, enum: constants.productTypes, required: true },
name: { type: String, required: false },
espData: {
type: { type: String, required: true },
text: { type: String, required: true }
},
error: {
type: {
message: { type: String, required: true },
statusCode: { type: Number, default: null }
},
required: false
}
// Some more definitions...
};
What's important from here is that I have collection of products where each product has its own type (which is a required string property that can have values defined in constants.productTypes), a non-required name field and so on. Also, there's espData field that has its own mandatory type property which is completely different from the top level type. And, there's error property that does not exist at all times, but when it does, it must have message property and optional statusCode property.
I now have to modify this schema so that espData becomes optional field since I may now have products that don't have this property. How do I do that? I tried couple of things, but none of them worked:
First, I modified espData so that it looks the same as error:
espData: {
type: {
type: { type: String, required: true },
text: { type: String, required: true }
},
required: false
},
But, this is not working, most probably because there's so many nested type properties. Funny thing is that it perfectly works for the error property which has the same structure as espData, but without nested type property. The code I used is
const model = new this.factories.product.model();
model.type = 'hi-iq';
// model.espData purposely left undefined
await model.save();
The error I'm getting is Product validation failed: espData.type.text: Path 'espData.type.text' is required., espData.type.type: Path 'espData.type.type' is required. This indicates that model created from schema is created as espData.type.type which is not what I wanted (I wanted espData.type).
Second, I have tried the same from above, just instead of required field, I wrote: default: null which gave me an error TypeError: Invalid value for schema path 'espData.default', got value "null".
So, how do I define espData as an optional field, which must have type and text properties when it exists?
Is this what you want. Create a new Document Schema with all the validations and nest it in another Schema with required: false (its default to false anyway)
const EspDataSchema = new Schema({
type: { type: String, required: true },
text: { type: String, required: true },
},
{
_id: false,
}
);
example
export const ProductSchema = new Schema({
...
espData: {
type: EspDataSchema,
required: false
},
...
})

How to solve circular dependency in model definition in node?

I'm following MVC architecture using express and mongoose and I came upon an issue with circular-dependency. The code itself is written in ES6.
I have these two particular models (keep in mind that I obscured these models as much a possible):
Destination model, which contains information about all available rooms:
// destination.model.js
import mongoose, { Schema } from 'mongoose';
import Booking from './booking.model'; // eslint detect dependency cycle here
import Room from './room.model';
const DestinationSchema = new Schema({
id: { type: Number, required: true },
name: { type: String, required: true, max: 100 },
description: { type: String, required: false },
rooms: [Room.schema]
});
DestinationSchema.statics.getAvailableRooms = async function (startDate, endDate) {
const bookings = await Booking.find({ 'room._id': room._id });
// do something with these bookings
};
export default mongoose.model('Destination', DestinationSchema);
and Booking model, which is in relation many to one with Destination.
// booking.model.js
import mongoose, { Schema } from 'mongoose';
import Destination from './destination.model';
import Room from './room.model';
const BookingSchema = new Schema({
id: { type: Number, required: true },
client: { type: String, required: true },
startDate: { type: Date, default: '' },
endDate: { type: Date, default: '' },
room: { type: Room.schema, required: false },
destination: Destination.schema
});
export default mongoose.model('Booking', BookingSchema);
Main issue:
ESLint detects dependency cycle in destination model (and in booking model) - which is present. The reason for that, is that I have a static method in Destination model, which looks over all Bookings (and possibly might call a static method in the future).
My question (or actually looking for an advice) is, how do I handle this issue? I come from Ruby on Rails background, so I'm really used to having both instance and static methods defined in a model, with single file.
I don't want to separate methods into another file and I would like to keep them in single file - is that possible in any way or should I really go for file separation?
Cheers
I think you should model booking schema like:
const BookingSchema = new Schema({
id: { type: Number, required: true },
client: { type: String, required: true },
startDate: { type: Date, default: '' },
endDate: { type: Date, default: '' },
room: { type: Room.schema, required: false },
destination: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Destination',
},
});

Mongoose typings, id property not available

I used the following code for my mongoose TypeScript model.
import mongoose = require('mongoose')
export interface PieceInterface extends mongoose.Document {
date: Date
summary: string
source: string
link: string
}
export const PieceSchema = new mongoose.Schema({
date: { type: Date, default: new Date(), required: true },
summary: { type: String, default: 'summary', required: true },
source: { type: String, default: 'source', required: true },
link: { type: String, default: '#', required: true }
})
export const Piece = mongoose.model<PieceInterface>('Piece', PieceSchema)
Now, when I use Piece.find({}).then(x => {...}), x is a Promise<PieceInterface[]> as expected.
However, I am not able to get
Piece.find({}).then(x => {
console.log(x[0].id)
})
to compile properly, it always tells me error TS2339: Property 'id' does not exist on type 'PieceInterface'.
Since, PieceInterface extends mongoose.Document, I looked...
interface Document extends MongooseDocument, NodeJS.EventEmitter, ModelProperties
And then,
class MongooseDocument implements MongooseDocumentOptionals
And finally this,
interface MongooseDocumentOptionals {
id?: string;
}
And in my mind, it should work. I am able to use variables provided by MongooseDocument and ModelProperties, but not MongooseDocumentOptionals.
Anything I'm missing?
References: mongoose.d.ts from GitHub
Definition of document
Definition of MongooseDocument
Definition of MongooseDocumentOptionals
Default field name for the ID in Mongo is _id not id, so you should do:
Piece.find({}).then(x => {
console.log(x[0]._id)
})
my friend,
Use this mechanism,
import mongoose = require('mongoose')
export interface PieceInterface extends mongoose.Document {
id : mongoose.Types.ObjectId //Add this line
date: Date
summary: string
source: string
link: string
}
export const PieceSchema = new mongoose.Schema({
date: { type: Date, default: new Date(), required: true },
summary: { type: String, default: 'summary', required: true },
source: { type: String, default: 'source', required: true },
link: { type: String, default: '#', required: true }
})
export const Piece = mongoose.model<PieceInterface>('Piece', PieceSchema)

Resources