"VersionError: No matching document found" error on Node.js/Mongoose - node.js

I'm relatively new to Node.js and Mongo/Mongoose, and I'm having a very difficult time troubleshooting a specific Mongoose error:
VersionError: No matching document found.
(Entire error trace/stack at the bottom of this question.)
This blog post pretty clearly outline how a VersionError might occur:
http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html
(TL;DR - "Mongoose v3 now adds a schema-configurable version key to each document. This value is atomically incremented whenever a modification to an array potentially changes any array’s elements position." If you try to save a document, but the version key no longer matches the object you have retrieved, you get the above VersionError.)
Core Question: Is there some way to display the offending save() operation? Or which document failed to save? Or anything at all?! ;)
The Challenge: this is a relatively large code base with many arrays, and I am unsure how to begin to troubleshoot the problem. In particular, the error trace/stack does NOT seem to show where the problem exists. See below:
VersionError: No matching document found.
at handleSave (<project_path>/node_modules/mongoose/lib/model.js:121:23)
at exports.tick (<project_path>/node_modules/mongoose/lib/utils.js:408:16)
at null.<anonymous> (<project_path>/node_modules/mongoose/node_modules/mongodb/lib/mongodb/collection.js:484:9)
at g (events.js:192:14)
at EventEmitter.emit (events.js:126:20)
at Server.Base._callHandler (<project_path>/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:391:25)
at Server.connect.connectionPool.on.server._serverState (<project_path>/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:558:20)
at MongoReply.parseBody (<project_path>/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:131:5)
at Server.connect.connectionPool.on.server._serverState (<project_path>/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:517:22)
at EventEmitter.emit (events.js:96:17)

Per request, here is an outline of our problem, and how we resolved it:
In our system we created a custom Document locking routine (using redis-lock), wherein the following happened in this precise (incorrect) order:
INCORRECT ORDER OF OPERATIONS:
Client request received
Document locked
Document retrieved
Document edited
Document unlocked
Client request resolved
Document saved
Once you see it written out, the problem is obvious: we were saving our Documents outside our Document lock.
Let's assume #6 takes 100ms in our system. That is a 100ms window wherein if any other requests grabs that same Document, we're going to have a save conflict (the titled error in this Question is basically a save conflict IMHO).
In other words/example: in our system, Request A grabbed Version 1 of Document X, edited it, then unlocked it, but before Request A saved the Document, Request B grabbed Document X and incremented it to Version 2 (read up on Mongo versions for more info about this). Then Request A resolves its Client request and goes to save Document X, but it's trying to save Version 1, and now it sees it has Version 2, and thus the error above.
So the fix is easy. Save your Documents inside your lock. (In the above example, move #7 to before #5. See below.)
CORRECT/FIXED ORDER OF OPERATIONS
Client request received
Document locked
Document retrieved
Document edited
Document saved
Document unlocked
Client request resolved
(You could make an argument that #6 and #7 should be swapped, but that is outside the scope of Mongo/Mongoose/this question.)
I am going to leave this question un-answered for a while and see if anyone can shed some light on a better way to isolate the relevant code and troubleshoot this issue. In our case, this was a very systemic problem and VERY challenging to troubleshoot for our skill level at the time.

It propably points to saving the same document concurrently as robertklep points out.
We had a similar issue running concurrent saves on the same document using async.parallel.

This error can also occur when your process maintains an outdated version of a document in memory and then tries to save it at some point after it has been updated by another process.

I had the same error when i tried to update a user's reference IDs to an email. The fix was really simple with async / await! Here the code snippet, hope it helps.
email
.save()
.then(() =>
User.findById(email.from).then(async sender => { // declare function as async
sender.emails.sent.push(email._id);
await sender.save(); // wait for save() to complete before proceeding
}).catch((err) => console.log(err))
)
.then(() =>
User.findById(email.to).then(async receiver => { // same as above
receiver.emails.received.push(email._id);
await receiver.save(); // same as above
}).catch((err) => console.log(err))
)
.then(() => res.status(200).json({ message: successMessage }))
.catch(err => console.log(err));

I had this issue because I was removing an item from an array of a Document using splice function.
I fixed replacing splice function with pull function from mongoose.

Related

fspromises.writeFile() Writes Empty File on process.exit()

I've been looking all over, but I can't seem to find the answer why I'm getting nothing in the file when exiting.
For context, I'm writing a discord bot. The bot stores its data once an hour. Sometime between stores I want to store the data in case I decide I want to update the bot. When I manually store the data with a command, then kill the process, things work fine. Now, I want to be able to just kill the process without having to manually send the command. So, I have a handler for SIGINT that stores the data the same way I was doing manually and after the promise is fulfilled, I exit. For some reason, the file contains nothing after the process ends. Here's the code (trimmed).
app.ts
function exit() {
client.users.fetch(OWNER)
.then(owner => owner.send('Rewards stored. Bot shutting down'))
.then(() => process.exit());
}
process.once('SIGINT', () => {
currencyService.storeRewards().then(exit);
});
process.once('exit', () => {
currencyService.storeRewards().then(exit);
});
currency.service.ts
private guildCurrencies: Map<string, Map<string, number>> = new Map<string, Map<string, number>>();
storeRewards(): Promise<void[]> {
const promises = new Array<Promise<void>>();
this.guildCurrencies.forEach((memberCurrencies, guildId) => {
promises.push(this.storageService.store(guildId, memberCurrencies));
})
return Promise.all(promises)
}
storage.service.ts
store(guild: string, currencies: Map<string, number>): Promise<void> {
return writeFile(`${this.storageLocation}/${guild}.json`, JSON.stringify([...currencies]))
.catch(err => {
console.error('could not store currencies', err);
})
}
So, as you can see, when SIGINT is received, I get the currency service to store its data, which maps guilds to guild member currencies which is a map of guild members to their rewards. It stores the data in different files (each guild gets its own file) using the storage service. The storage service returns a promise from writeFile (should be a promise of undefined when the file is finished writing). The currency service accumulates all the promises and returns a promise that resolves when all of the store promises resolve. Then, after all of the promises are resolved, a message is sent to the bot owner (me), which returns a promise. After that promise resolves, then we exit the process. It should be a clean exit with all the data written and the bot letting me know that it's shutting down, but when I read the file later, it's empty.
I've tried logging in all sorts of different places to make sure the steps are being done in the right order and I'm not getting weird async stuff, and everything seems to be proceeding as expected, but I'm still getting an empty file. I'm not sure what's going on, and I'd really appreciate some guidance.
EDIT: I remembered something else. As another debugging step, I tried reading the files after the currency service storeRewards() promise resolved, and the contents of the files were valid* (they contained valid data, but it was probably old data as the data doesn't change often). So, one of my thoughts is that the promise for writeFile resolves before the file is fully written, but that isn't indicated in the documentation.
EDIT 2: The answer was that I was writing twice. None of the code shown in the post or the first edit would have made it clear that I was having a double write issue, so I am adding the code causing the issue so that future readers can get the same conclusion.
Thanks to #leitning for their help finding the answer in the comments on my question. After writing a random UUID in the file name, I found the file was being written twice. I had assumed when asking the question, that I had shared all the relevant info, but I had missed something. process.once('exit', ...) was being called after calling process.exit() (more details here). The callback function for the exit event does not handle asynchronous calls. When the callback function returns, the process exits. Since I had duplicated the logic in the SIGINT callback function in the exit callback function, the file was being written a second time and the process was exiting before the file could be written, resulting in an empty file. Removing the process.once('exit', ...) logic fixed the issue.

Strapi & react-admin : I'd like to set 'Content-Range' header dynamically when any fetchAll query fires

I'm still a novice web developer, so please bear with me if I miss something fundamental !
I'm creating a backoffice for a Strapi backend, using react-admin.
React-admin library uses a 'data provider' to link itself with an API. Luckily someone already wrote a data provider for Strapi. I had no problem with step 1 and 2 of this README, and I can authenticate to Strapi within my React app.
I now want to fetch and display my Strapi data, starting with Users. In order to do that, quoting Step 3 of this readme : 'In controllers I need to set the Content-Range header with the total number of results to build the pagination'.
So far I tried to do this in my User controller, with no success.
What I try to achieve:
First, I'd like it to simply work with the ctx.set('Content-Range', ...) hard-coded in the controller like aforementioned Step 3.
Second, I've thought it would be very dirty to c/p this logic in every controller (not to mention in any future controllers), instead of having some callback function dynamically appending the Content-Range header to any fetchAll request. Ultimately that's what I aim for, because with ~40 Strapi objects to administrate already and plenty more to come, it has to scale.
Technical infos
node -v: 11.13.0
npm -v: 6.7.0
strapi version: 3.0.0-alpha.25.2
uname -r output: Linux 4.14.106-97.85.amzn2.x86_64
DB: mySQL v2.16
So far I've tried accessing the count() method of User model like aforementioned step3, but my controller doesn't look like the example as I'm working with users-permissions plugin.
This is the action I've tried to edit (located in project/plugins/users-permissions/controllers/User.js)
find: async (ctx) => {
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
// Send 200 `ok`
ctx.send(data);
},
From what I've gathered on Strapi documentation (here and also here), context is a sort of wrapper object. I only worked with Express-generated APIs before, so I understood this snippet as 'use fetchAll method of the User model object, with ctx.query as an argument', but I had no luck logging this ctx.query. And as I can't log stuff, I'm kinda blocked.
In my exploration, I naively tried to log the full ctx object and work from there:
// Send 200 `ok`
ctx.send(data);
strapi.log.info(ctx.query, ' were query');
strapi.log.info(ctx.request, 'were request');
strapi.log.info(ctx.response, 'were response');
strapi.log.info(ctx.res, 'were res');
strapi.log.info(ctx.req, 'were req');
strapi.log.info(ctx, 'is full context')
},
Unfortunately, I fear I miss something obvious, as it gives me no input at all. Making a fetchAll request from my React app with these console.logs print this in my terminal:
[2019-09-19T12:43:03.409Z] info were query
[2019-09-19T12:43:03.410Z] info were request
[2019-09-19T12:43:03.418Z] info were response
[2019-09-19T12:43:03.419Z] info were res
[2019-09-19T12:43:03.419Z] info were req
[2019-09-19T12:43:03.419Z] info is full context
[2019-09-19T12:43:03.435Z] debug GET /users?_sort=id:DESC&_start=0&_limit=10& (74 ms)
While in my frontend I get the good ol' The Content-Range header is missing in the HTTP Response message I'm trying to solve.
After writing this wall of text I realize the logging issue is separated from my original problem, but if I was able to at least log ctx properly, maybe I'd be able to find the solution myself.
Trying to summarize:
Actual problem is, how do I set my Content-Range properly in my strapi controller ? (partially answered cf. edit 3)
Collateral problem n°1: Can't even log ctx object (cf. edit 2)
Collateral problem n°2: Once I figure out the actual problem, is it feasible to address it dynamically (basically some callback function for index/fetchAll routes, in which the model is a variable, on which I'd call the appropriate count() method, and finally append the result to my response header)? I'm not asking for the code here, just if you think it's feasible and/or know a more elegant way.
Thank you for reading through and excuse me if it was confuse; I wasn't sure which infos would be relevant, so I thought the more the better.
/edit1: forgot to mention, in my controller I also tried to log strapi.plugins['users-permissions'].services.user object to see if it actually has a count() method but got no luck with that either. Also tried the original snippet (Step 3 of aforementioned README), but failed as expected as afaik I don't see the User model being imported anywhere (the only import in User.js being lodash)
/edit2: About the logs, my bad, I just misunderstood the documentation. I now do:
ctx.send(data);
strapi.log.info('ctx should be : ', {ctx});
strapi.log.info('ctx.req = ', {...ctx.req});
strapi.log.info('ctx.res = ', {...ctx.res});
strapi.log.info('ctx.request = ', {...ctx.request});
ctrapi.log.info('ctx.response = ', {...ctx.response});
Ctx logs this way; also it seems that it needs the spread operator to display nested objects ({ctx.req} crash the server, {...ctx.req} is okay). Cool, because it narrows the question to what's interesting.
/edit3: As expected, having logs helps big time. I've managed to display my users (although in the dirty way). Couldn't find any count() method, but watching the data object that is passed to ctx.send(), it's equivalent to your typical 'res.data' i.e a pure JSON with my user list. So a simple .length did the trick:
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
ctx.set('Content-Range', data.length) // <-- it did the trick
// Send 200 `ok`
ctx.send(data);
Now starting to work on the hard part: the dynamic callback function that will do that for any index/fetchAll call. Will update once I figure it out
I'm using React Admin and Strapi together and installed ra-strapi-provider.
A little boring to paste Content-Range header into all of my controllers, so I searched for a better solution. Then I've found middleware concept and created one that fits my needs. It's probably not the best solution, but do its job well:
const _ = require("lodash");
module.exports = strapi => {
return {
// can also be async
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
if (_.isArray(ctx.response.body))
ctx.set("Content-Range", ctx.response.body.length);
});
}
};
};
I hope it helps
For people still landing on this page:
Strapi has been updated from #alpha to #beta. Care, as some of the code in my OP is no longer valid; also some of their documentation is not up to date.
I failed to find a "clever" way to solve this problem; in the end I copy/pasted the ctx.set('Content-Range', data.length) bit in all relevant controllers and it just worked.
If somebody comes with a clever solution for that problem I'll happily accept his answer. With the current Strapi version I don't think it's doable with policies or lifecycle callbacks.
The "quick & easy fix" is still to customize each relevant Strapi controller.
With strapi#beta you don't have direct access to controller's code: you'll first need to "rewrite" one with the help of this doc. Then add the ctx.set('Content-Range', data.length) bit. Test it properly with RA, so for the other controllers, you'll just have to create the folder, name the file, copy/paste your code + "Search & Replace" on model name.
The "longer & cleaner fix" would be to dive into the react-admin source code and refactorize so the lack of "Content-Range" header doesn't break pagination.
You'll now have to maintain your own react-admin fork, so make sure you're already committed into this library and have A LOT of tables to manage through it (so much that customizing every Strapi controller will be too tedious).
Before forking RA, please remember all the stuff you can do with the Strapi backoffice alone (including embedding your custom React app into it) and ensure it will be worth the trouble.

tableSvc.retrieveEntity retrieves previous version of Azure Table Storage data

I have a waterfall of dialogs in Bot Framework SDK3,
each dialog does something, until it switches to dialog with tableSvc.retrieveEntity which correctly identifies a required to be retrieved entity (according to given PartionKey & RowKey) from Azure Table...
...but the entity which is retreived (I check it with console.log('Result') is outdated (one step [a few seconds, which pass during conversation of user with a bot] behind the actual data stored in Azure Tables - the real data which needs to be retrieved in this dialog...)
The Conversation is not closed yet (it will be later) - it is important to store and retrieve actual data at this stage...
How to get actual data in this dialog?
Well, for those of you who had similar problem...
I guess, it has to do with event loop of Node.js...
I'm not sure whether it is a bullet-proof solution, or a temporary 'hack',
but I put it like this and it works (when I try to use setTimeout 0 ms - it does not work for me, when I set it to 500ms - it works, so I guess 1000 ms could be a safe temporary hack..before I find better solution)..
If someone knows a better, more robust, solution, please, update this thread.
setTimeout( () => {
tableSvc.retrieveEntity('table', pkey, rkey, funcdtion(error, result, response) {
if(!error) {
var res1 = result.Data._;
console.log(res1); // Now it prints actual data stored in 'table' - which I really need, and not its previous (outdated) version
} else {
console.log('Some error happened...');
};
});
}, 1000);

Firestore: get document back after adding it / updating it without additional network calls

Is it possible to get document back after adding it / updating it without additional network calls with Firestore, similar to MongoDB?
I find it stupid to first make a call to add / update a document and then make an additional call to get it.
As you have probably seen in the documentation of the Node.js (and Javascript) SDKs, this is not possible, neither with the methods of a DocumentReference nor with the one of a CollectionReference.
More precisely, the set() and update() methods of a DocumentReference both return a Promise containing void, while the CollectionReference's add() method returns a Promise containing a DocumentReference.
Side Note (in line with answer from darrinm below): It is interesting to note that with the Firestore REST API, when you create a document, you get back (i.e. through the API endpoint response) a Document object.
When you add a document to Cloud Firestore, the server can affect the data that is stored. A few ways this may happen:
If your data contains a marker for a server-side timestamp, the server will expand that marker into the actual timestamp.
Your data data is not permitted according to your server-side security rules, the server will reject the write operation.
Since the server affects the contents of the Document, the client can't simply return the data that it already has as the new document. If you just want to show the data that you sent to the server in your client, you can of course do so by simply reusing the object you passed into setData(...)/addDocument(data: ...).
This appears to be an arbitrary limitation of the the Firestore Javascript API. The Firestore REST API returns the updated document on the same call.
https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/patch
I did this to get the ID of a new Document created, and then use it in something else.
Future<DocumentReference<Object>> addNewData() async {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final CollectionReference _userCollection = _firestore.collection('users');
return await _userCollection
.add({ 'data': 'value' })
.whenComplete(() => {
// Show good notification
})
.catchError((e) {
// Show Bad notification
});
}
And here I obtain the ID:
await addNewData()
.then((document) async {
// Get ID
print('ID Document Created ${document.id}');
});
I hope it helps.

How do I save an updated CouchDB doc with Evently?

I'm following IBM's tutorial on CouchDB and ran into a problem saving edited documents. Saving a new doc works fine, and it looks like all my existing doc's values (retrieved via the openDoc function) are valid, but the new values are simply not taking.
As I trace through the executing code, it looks like the new values are there:
However, after saveDoc returns successfully (strange!), the doc is not updated. If I log the updated doc, the logged object has no _id or _rev values, though you can clearly see they were present while saving (see first screenshot).
Is there some nuance of saveDoc, or perhaps Evently, that I'm not understanding?
In your success callback, you are not logging the updated document, your are logging the newdoc object from line 87 (minus it went through the saveDoc function, which apparently undefined _id and _rev, but that's ok).
The success callback can have an argument that is the response from the server. If you write :
success: function(data) {
console.log(data);
}
you should see in the response a new _rev starting with 2-.
If you really want to see the full doc once updated, just call openDoc from your success callback to get the updated version.
Other detail, when you say
saveDoc returns successfully
I think you meant that the success callback is called, because saveDoc is asynchronous and should return instantly anyway.

Resources