Comments (56)
One thing that comes to mind is a circular dependency, so when @Pre
is executed the function isn't resolved yet. You could try putting the entire Contact
class definition (everything after the imports) in a setTimeout()
and see if the issue still happens. If not, then you probably have a circular dependency
from typegoose.
Results in
[INFO] 13:05:05 Restarting: manual restart from user
Using ts-node version 8.1.0, typescript version 3.6.3
SaveNote Sync: undefined
Express server running on port: 4000
(node:21388) UnhandledPromiseRejectionWarning: TypeError: "Contact.pre.save"'s function is not a function!
at addToHooks (F:\prosperty\server\node_modules\@typegoose\typegoose\src\hooks.ts:101:11)
at F:\prosperty\server\node_modules\@typegoose\typegoose\src\hooks.ts:77:29
at DecorateConstructor (F:\prosperty\server\node_modules\reflect-metadata\Reflect.js:541:33)
at Object.decorate (F:\prosperty\server\node_modules\reflect-metadata\Reflect.js:130:24)
at Object.__decorate (F:\prosperty\server\node_modules\tslib\tslib.js:89:96)
at Object.<anonymous> (F:\prosperty\server\src\entity\contact\contact.entity.ts:16:21)
at Module._compile (internal/modules/cjs/loader.js:816:30)
at Module._compile (F:\prosperty\server\node_modules\source-map-support\source-map-support.js:521:25)
at Module.m._compile (C:\Users\RYANNG~1\AppData\Local\Temp\ts-node-dev-hook-7129417309517636.js:57:25)
at Module._extensions..js (internal/modules/cjs/loader.js:827:10)
at require.extensions.(anonymous function) (C:\Users\RYANNG~1\AppData\Local\Temp\ts-node-dev-hook-7129417309517636.js:59:14)
at Object.nodeDevHook [as .ts] (F:\prosperty\server\node_modules\ts-node-dev\lib\hook.js:61:7)
at Module.load (internal/modules/cjs/loader.js:685:32)
at Function.Module._load (internal/modules/cjs/loader.js:620:12)
at Module.require (internal/modules/cjs/loader.js:723:19)
at require (internal/modules/cjs/helpers.js:14:16)
(node:21388) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:21388) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Redis server connected
SaveNote Async: undefined
Database connection established. mongodb+srv://ryann:[email protected]/trade_graphql?retryWrites=true
(node:21388) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
null
Here it is when i do not use the union"
[INFO] 13:07:23 Restarting: manual restart from user
Using ts-node version 8.1.0, typescript version 3.6.3
SaveNote Sync: function () {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.editedDate = defaults_helper_1.UnixSeconds();
const isNewDoc = this.editedDate - this.createdDate < 5;
const modifiedPaths = this.modifiedPaths().filter(res => res !== 'editedDate').map(res => _.startCase(res)).join(', ');
const { firstName, lastName } = yield user_entity_1.UserModel.findById(this.createdUser);
let input = {
entity: this._id,
entityRef: this.constructor.modelName,
body: isNewDoc ? `Created by ${firstName} ${lastName}` : `'${modifiedPaths}', Updated by ${firstName} ${lastName}`
};
yield new note_entity_1.NoteModel(Object.assign({}, input)).save();
});
}
Express server running on port: 4000
Redis server connected
SaveNote Async: function () {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.editedDate = defaults_helper_1.UnixSeconds();
const isNewDoc = this.editedDate - this.createdDate < 5;
const modifiedPaths = this.modifiedPaths().filter(res => res !== 'editedDate').map(res => _.startCase(res)).join(', ');
const { firstName, lastName } = yield user_entity_1.UserModel.findById(this.createdUser);
let input = {
entity: this._id,
entityRef: this.constructor.modelName,
body: isNewDoc ? `Created by ${firstName} ${lastName}` : `'${modifiedPaths}', Updated by ${firstName} ${lastName}`
};
yield new note_entity_1.NoteModel(Object.assign({}, input)).save();
});
}
Database connection established. mongodb+srv://ryann:[email protected]/trade_graphql?retryWrites=true
(node:20028) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
null
from typegoose.
@NickBolles thanks again for pointing this out
from typegoose.
for some that have the same issue, here is a blog post that has an ok workaround, that dosnt need to use "require on the fly"
from typegoose.
Sweet! I had a suspicion. But I'm surprised the setTimeout() didn't print it.
That blog post is good too.But yea, probably not worth the time.
from typegoose.
did you already test it with 6.0.0-x?
btw, what for an import thing do you use there (i ask because the @something
(for paths) dosnt look right)
otherwise i dont see any problem how this could happen
PS: this is the wrong repo for this issue (you use @hasezoey/typegoose
, not typegoose
)
from typegoose.
after a test, i could not reproduce it in 5.9.2 or 6.0.0-28
import { pre, prop, Typegoose } from "@hasezoey/typegoose"; // @hasezoey/[email protected]
import * as mongoose from "mongoose"; // [email protected]
async function hello() {
console.log("hello run");
}
@pre<ONE>("save", hello)
class ONE extends Typegoose {
@prop()
public something?: string;
}
const ONEModel = new ONE().getModelForClass(ONE);
@pre<TWO>("save", hello)
class TWO extends Typegoose {
@prop()
public something?: string;
}
const TWOModel = new TWO().getModelForClass(TWO);
(async () => {
await mongoose.connect(`mongodb://localhost:27017/`, { useNewUrlParser: true, dbName: "verifyS392" });
{
const doc = new ONEModel();
await doc.save();
console.log("ONE run");
}
{
const doc = new TWOModel();
await doc.save();
console.log("TWO run");
}
await mongoose.disconnect();
})();
from typegoose.
I'm still getting on the newest 6 version, but it seems it's only is happening when I include the last line "await new NoteModel({...input}).save()", if I remove this line it seems to compile.
On the latest 6 versionI'm also getting
(node:19124) DeprecationWarning: The Typegoose Class is deprecated, please try to remove it
(node:19124) DeprecationWarning: Typegoose Class is Deprecated!
repeatedly
from typegoose.
On the latest 6 versionI'm also getting
thats because in v6.0.0 extends Typegoose
is not needed anymore, so its deprecated and will be removed in future version
await new NoteModel({...input}).save()
could you try await NoteModel.create({...input})
from typegoose.
could you edit your last comment to use a full code-block and not an inline-code-block?
otherwise, could you give me the @pre
line it errors on?
from typegoose.
If I duplicate and move all exported function directly into my @pre functions it seems to operate as normal.
(node:9872) UnhandledPromiseRejectionWarning: TypeError: "Supplier.pre.save"'s function is not a function!
at addToHooks (F:\prosperty\server\node_modules\@hasezoey\typegoose\src\hooks.ts:101:11)
at F:\prosperty\server\node_modules\@hasezoey\typegoose\src\hooks.ts:77:29
at DecorateConstructor (F:\prosperty\server\node_modules\reflect-metadata\Reflect.js:541:33)
at Object.decorate (F:\prosperty\server\node_modules\reflect-metadata\Reflect.js:130:24)
at Object.__decorate (F:\prosperty\server\node_modules\tslib\tslib.js:89:96)
at Object.<anonymous> (F:\prosperty\server\src\entity\supplier\supplier.entity.ts:13:22)
at Module._compile (internal/modules/cjs/loader.js:816:30)
at Module._compile (F:\prosperty\server\node_modules\source-map-support\source-map-support.js:521:25)
at Module.m._compile (C:\Users\RYANNG~1\AppData\Local\Temp\ts-node-dev-hook-5900565580456727.js:56:25)
at Module._extensions..js (internal/modules/cjs/loader.js:827:10)
at require.extensions.(anonymous function) (C:\Users\RYANNG~1\AppData\Local\Temp\ts-node-dev-hook-5900565580456727.js:58:14)
at Object.nodeDevHook [as .ts] (F:\prosperty\server\node_modules\ts-node-dev\lib\hook.js:61:7)
at Module.load (internal/modules/cjs/loader.js:685:32)
at Function.Module._load (internal/modules/cjs/loader.js:620:12)
at Module.require (internal/modules/cjs/loader.js:723:19)
at require (internal/modules/cjs/helpers.js:14:16)
from typegoose.
I use the "path" field in my tsconfig.json to shorten my paths as shown here, even if i changed my import paths to a 'normal' import climbing the file tree I still get the error.
"paths": {
"@core/*": ["./core/*"],
"@entity/*": ["./entity/*"],
"@api/*": ["./api/*"]
}
import { Note, NoteModel } from '@entity/note/note.entity'
import { PolygonGeometry } from '@entity/geometry/polygonGeometry/polygonGeometry.entity'
import { CreatedTypegoose } from '@entity/createdTypegoose.extends'
import { PropertyTypeEnum } from '@core/enums/propertyType.enum'
from typegoose.
when i can trust the error, then the problem is @pre(method: given, function: missing)
, so could you provide the line where @pre
is called + the function("header")?
from typegoose.
I'm not exactly sure what you mean Hase,
After doing some more digging in hooks.js, it seems that the pre method never gets the function once I use the @pre decorator more than three times when I include the "new NoteModel.save()" line.
When I remove that line it works every time.
I also changed my await statements to normal promise's and noticed no difference.
from typegoose.
example:
@pre<TWO>("save", hello) // can you give me THIS line at where it errors
class TWO extends Typegoose {
@prop()
public something?: string;
}
// and
async function hello() { // i mean this line as "function header"
console.log("hello run");
}
from typegoose.
@ObjectType()
@pre<Job>('save', PreSaveNote)
export class Job extends CreatedTypegoose { }
@ObjectType()
@Index({ center: '2dsphere' })
@Index({ addressString: 'text' })
@Index({ addressId: 1, createdBusiness: 1 }, { unique: true })
@pre<Property>('save', PreSaveNote)
export class Property extends CreatedTypegoose { }
@ObjectType()
@pre<Supplier>('save', PreSaveNote)
export class Supplier extends CreatedTypegoose { }
@ObjectType()
@Index({name: 'text', surname: 'text', email: 'text'})
@pre<Contact>('save', PreSaveNote)
export class Contact extends CreatedTypegoose { }
export const PreSaveNote = async function() {
const { user, business } = httpContext.get('state')
if (!user) return
const { firstName, lastName } = await UserModel.findById(user)
const modifiedPaths = this.modifiedPaths()
const isNewDoc = modifiedPaths.includes('createdUser')
this.editedDate = UnixSeconds()
let input: Partial<Note> = {
createdUser: user,
createdBusiness: business,
entity: this._id,
entityRef: this.constructor.modelName,
body: isNewDoc ? `Created by ${firstName} ${lastName}` : `'${modifiedPaths.map(res => _.startCase(res)).join(', ')}', Updated by ${firstName} ${lastName}`
}
new NoteModel({...input}).save()
}
If I remove the decorator from 'Job', 'Property' & 'Supplier'. It works.
from typegoose.
question: does the following change affect anything?
// from
export const PreSaveNote = async function() {
// to
export async function PreSaveNote() {
from typegoose.
No luck on my end, trying many different export types.
from typegoose.
i cant see a logical reason why this should happen, i will retry to reproduce it...
from typegoose.
Yea it feels very odd to me, perhaps some outside issue here, would an older typegoose help?
from typegoose.
would an older typegoose help?
since that 5.9.x didnt work, an older wouldnt work too
from typegoose.
@RyannGalea now i tried 3 classes, same function each class & function in their own file (like you have), but it still works, i just cant reproduce it
here is what i have done to test
do you see anything that you do different?
from typegoose.
Just ran through your example and it runs fine. This is tricky, I'm not sure what is affecting it.
from typegoose.
Ok,
So I have spent the last few hours on this and have finally fixed my issue I believe.
As Hasezoey pointed out I was using paths in my tsconfig.json to shorten & clean up my imports. I did this mainly to align the server code with my Angular code. When using @ paths typescript cannot resolve the module imports with tsc, so you have to use another package called 'tsconfig-paths' which resolves all of the @ paths when compiling.
I since removed all my @ path entries along with the 'tsconfig-paths' package and changed all my import paths to normal relative imports & it seems to have resolved my problems.
I think when 'tsconfig-paths' was resolving the imports it was causing some sort of conflict, so now it seems to be working.
Thanks for your help @hasezoey, long night of zero progress. lol
from typegoose.
@RyannGalea i will add it to the "known issues" section as a warning for future users
because it is solved now, as a "not an typegoose issue", could you close it?
from typegoose.
The issue seems to be back, investigating some more.
from typegoose.
what is causing the issue this time?... /s
i would try to help, is it with the same example again?
from typegoose.
I'm pretty sure that I am narrowing it down to a conflict with 'type-graphql' package & union types.
It's hard to track down because It's so intermittent.
from typegoose.
@RyannGalea any news on this?
from typegoose.
I have opened an issue with 'type-graphql' but have not had the time to investigate the issue any further.
I have removed the need to use the union in my schema for the time being to continue development.
from typegoose.
@RyannGalea do you have a link to the repo? there shouldn't be any issues with tsconfig paths. If you link me a repo that I can debug I can look into it.
from typegoose.
I will delve back into this tomorrow evening
from typegoose.
when you test it again, did version 6.1.0-6
(or higher) change anything?
from typegoose.
So with version '6.1.0-6' I still get the
UnhandledPromiseRejectionWarning: TypeError: "Contact.pre.save"'s function is not a function!
when using the following code:
@Field(type => NoteEnityUnion)
@prop({ required: true, refPath: 'entityRef' })
entity: Ref<Property | Contact | Supplier | User>
The type-graphql code:
export const NoteEnityUnion = createUnionType({
name: 'NoteEnityUnion',
types: () => [Property, BusinessProperty, Supplier, User],
resolveType: value => {
if ('contactType' in value) return Contact
if ('buildingType' in value) return Property
if ('supplierType' in value) return Supplier
if ('firstName' in value) return User
if ('property' in value && 'addressString' in value) return BusinessProperty
return undefined
}
})
I just type it as ObjectId to continue working on it at the moment
@Field(type => ObjectId)
@prop({ required: true, refPath: 'entityRef' })
entity: Ref<Property | Contact | Supplier | User>
from typegoose.
@RyannGalea i dont know type-graphql, but i just installed it to test this case again - could you provide me the class CreatedTypegoose
? (couldnt find it anymore)
and a dummy User
?
and what is httpContext
/ how to "mock" it?
and what is _
for a package? lodash
? underscore
?
from typegoose.
import * as _ from 'lodash'
CreatedTypegoose is now named 'Globalfields and looks like this, the defaults just return a number / strings:
@ObjectType()
export class GlobalFields {
@Field()
readonly _id: ObjectId
@Field()
@prop({index: true, default: () => UnixSeconds() })
editedDate: number
@Field()
@prop({index: true, default: () => UnixSeconds()})
createdDate: number
@Field(type => User)
@prop({index: true, ref: 'User', default: () => HttpContext('user')})
createdUser: Ref<User>
@Field(type => Business)
@prop({index: true, ref: 'Business', default: () => HttpContext('business')})
createdBusiness: Ref<Business>
}
httpContext is the package 'express-http-context', i believe this was all an issue before i was even using this package:
Below is where context is set for using 'apollo-server-express' which just sets up my graphql server, type-graphql is just used to generate the schema. Context field is run on every request and is used to set the state in httpContext package, and state to be used in my classes using type-graphql @ctx
const schema = await generateSchema()
const server = new ApolloServer({
schema,
context: ({req, res}: Context) => {
const state = {...req.session.state}
httpContext.set('state', state)
return {req, res, state}
},
formatError: (err) => formatError(err)
})
const resolverPath = PATH.resolve(__dirname, '../../api/**/*.ts')
const emitSchemaPath = PATH.resolve(__dirname, '../../schema.readonly.gql')
export const generateSchema = () => {
return buildSchema({
resolvers: [resolverPath],
emitSchemaFile: emitSchemaPath,
globalMiddlewares: [TypegooseMiddleware],
scalarsMap: [{type: ObjectId, scalar: ObjectIdScalar}],
nullableByDefault: true,
authChecker,
container: Container
})
}
A User:
_id: 5cda9e3f2075bd1394a8ec32
emailConfirmed: true
mobileConfirmed:false
password: password
firstName:Ryann
lastName:Galea
email:[email protected]
from typegoose.
i cut down much (like commenting out the indexes, all httpcontext things, and all "UnixSeconds" calls), i still cant reproduce the your error with pre hooks ... but i didnt use type-graphql ... (i still search how to mock it without using express)
https://github.com/typegoose/typegoose-testing
from typegoose.
I will create a project to try and trim everything back to simply create the error,
from typegoose.
@RyannGalea Try this for me.
In the file that's causing the error do this to confirm that PreSaveNote is getting imported
// add the next two lines
console.log("PreSaveNote Sync:", PreSaveNote);
setTimeout(() => console.log("PreSaveNote Async:",PreSaveNote),1000)
@ObjectType()
@pre<Job>("save", PreSaveNote)
export class Job extends CreatedTypegoose { }
from typegoose.
thanks @NickBolles , this gives me a hint at least
from typegoose.
@RyannGalea it is not a problem with typegoose, what @NickBolles pointed out is that the function PreSaveNote
does not get imported the right way (it is undefined
as the test showed us)
PS: for DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead. null
you should use mongoose.connect('', { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true })
from typegoose.
@RyannGalea do you still use these @core/
& @entity/
like in your video?, when not, could you show it again?
from typegoose.
@RyannGalea Another troubleshooting step could be copy the PreSaveNote implementation into the file in question to eliminate any import issues.
Here's an update to your repo @hasezoey that has type-graphql running
https://github.com/NickBolles/typegoose-testing
from typegoose.
See repository here guys, i have stripped it back to the very basics and i still have the error on my end. Please let me know if it reproduces on your end.
https://github.com/RyannGalea/typegooseErr
from typegoose.
got it, tested it, found something:
when doing some debugging like setting some random prop export const s = "hello"
before anything else, it will show up in something like "JOB" when doing
import * as SN from "../core/saveNote.middleware";
console.log('from JOB', SN, SN.SaveNote);
result:
from JOB { s: "hello" } undefined
so it is something strange with js/ts import-export, not an typegoose issue
from typegoose.
From what i'm reading about this import issue is circular dependencies, so I'm curious if that's the issue here and how i would resolve it, so many imports between the entities and API
from typegoose.
i dont know, from what i have seen in the example repo, there isnt any circular dependencies between the imports of "SaveNote"...
from typegoose.
i think this can be closed again because it is not an typegoose issue?
-> if it happens again, please open a new issue in the new repo
from typegoose.
Ok, although it is not resolved i will close this for now.
from typegoose.
although it is not resolved
didnt you say the change exports/imports have fixed it?
from typegoose.
No i deleted that comment as it was a false flag.
from typegoose.
also when changing what you mentioned in the test repo, the compiled output didnt change
from typegoose.
nvm, found the circular dependencie:
saveNote.middleware -> note.entity -> Job -> saveNote.middleware
it is known in nodejs / js that this happens when it is circular
from typegoose.
Wow, we got there in the end, Thanks to @hasezoey & @NickBolles
So in my Pre function i removed the circular dependency import and moved it inside the function.
Before:
import { startCase } from 'lodash'
import { UserModel } from '../../entity/user/user.entity'
import { UnixSeconds } from '../helpers/defaults.helper'
import { NoteModel } from '../../entity/note/note.entity'
import { NoteInput } from '../../entity/note/note.input'
export const SaveNote = async function() {
this.editedDate = UnixSeconds()
const isNewDoc = this.editedDate - this.createdDate < 5
const modifiedPaths = this.modifiedPaths().filter(res => res !== 'editedDate').map(res => startCase(res)).join(', ')
const { firstName, lastName } = await UserModel.findById(this.createdUser)
let input: NoteInput = {
entity: this._id,
entityRef: this.constructor.modelName,
body: isNewDoc ? `Created by ${firstName} ${lastName}` : `'${modifiedPaths}', Updated by ${firstName} ${lastName}`
}
await new NoteModel({...input}).save()
}
After:
import { startCase } from 'lodash'
import { UserModel } from '../../entity/user/user.entity'
import { UnixSeconds } from '../helpers/defaults.helper'
import { NoteInput } from '../../entity/note/note.input'
export const SaveNote = async function() {
this.editedDate = UnixSeconds()
const isNewDoc = this.editedDate - this.createdDate < 5
const modifiedPaths = this.modifiedPaths().filter(res => res !== 'editedDate').map(res => startCase(res)).join(', ')
const { firstName, lastName } = await UserModel.findById(this.createdUser)
let input: NoteInput = {
entity: this._id,
entityRef: this.constructor.modelName,
body: isNewDoc ? `Created by ${firstName} ${lastName}` : `'${modifiedPaths}', Updated by ${firstName} ${lastName}`
}
const { NoteModel } = require('../../entity/note/note.entity')
await new NoteModel({...input}).save()
}
from typegoose.
I'll have a look at this @hasezoey, but seeing it seems to only be an issue inside of these pre functions i will likely stay with the require solution for now. I appreciate all the time spent on this.
from typegoose.
@hasezoey is this issue related to to alias paths ? it seems to me after reading the chain of discussion that it's related to circular dependencies.
if that's the case i suggest we remove the reference of this issue in the known issues as an issue with tsconfig-paths.
from typegoose.
Related Issues (20)
- Please use the new Repository to open new issues HOT 4
- TypeError: Class constructor Typegoose cannot be invoked without 'new' HOT 1
- [Request] Setting Collection Name and other Schema options via annotation and reflections
- [Question] Import ObjectId from Typegoose? HOT 4
- Please archive this repo
- Is there any way to achieve schema inheritance with typegoose? HOT 2
- [Question] Type of User.x property is not a y HOT 1
- Is it possible to use getModelForClass with generics? HOT 1
- [Question] HOT 1
- my populate field cant be queried out HOT 3
- [Question] HOT 1
- [Question] Problems reusing the same schema for both collection and subdocument HOT 1
- [Question] Can we add custom error messages when @prop options fail? HOT 2
- [Question] Are transactions supported for deleteMany? HOT 1
- [Question] Possible to have Query type support? HOT 1
- bi-directional virtuals population issue HOT 1
- Cannot use same model on two connections. HOT 2
- [Question] Is there a `required if` feature on model classes? HOT 2
- Both updatedBy and CreatedBy are getting updated after update operation on model object HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typegoose.