Skip to content

Commit 49bad32

Browse files
authored
Merge pull request #15430 from Automattic/vkarpov15/mongoose-lean-getters-44
fix(queryCursor): add _transformForAsyncIterator transform after user-defined transforms
2 parents 37134b0 + e1b629d commit 49bad32

File tree

3 files changed

+54
-43
lines changed

3 files changed

+54
-43
lines changed

lib/cursor/queryCursor.js

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ function QueryCursor(query) {
8383
// Max out the number of documents we'll populate in parallel at 5000.
8484
this.options._populateBatchSize = Math.min(this.options.batchSize, 5000);
8585
}
86+
if (query._mongooseOptions._asyncIterator) {
87+
this._mongooseOptions._asyncIterator = true;
88+
}
8689

8790
if (model.collection._shouldBufferCommands() && model.collection.buffer) {
8891
model.collection.queue.push([
@@ -379,29 +382,6 @@ QueryCursor.prototype.addCursorFlag = function(flag, value) {
379382
return this;
380383
};
381384

382-
/*!
383-
* ignore
384-
*/
385-
386-
QueryCursor.prototype.transformNull = function(val) {
387-
if (arguments.length === 0) {
388-
val = true;
389-
}
390-
this._mongooseOptions.transformNull = val;
391-
return this;
392-
};
393-
394-
/*!
395-
* ignore
396-
*/
397-
398-
QueryCursor.prototype._transformForAsyncIterator = function() {
399-
if (this._transforms.indexOf(_transformForAsyncIterator) === -1) {
400-
this.map(_transformForAsyncIterator);
401-
}
402-
return this;
403-
};
404-
405385
/**
406386
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js).
407387
* You do not need to call this function explicitly, the JavaScript runtime
@@ -433,19 +413,13 @@ QueryCursor.prototype._transformForAsyncIterator = function() {
433413
*/
434414

435415
if (Symbol.asyncIterator != null) {
436-
QueryCursor.prototype[Symbol.asyncIterator] = function() {
437-
return this.transformNull()._transformForAsyncIterator();
416+
QueryCursor.prototype[Symbol.asyncIterator] = function queryCursorAsyncIterator() {
417+
// Set so QueryCursor knows it should transform results for async iterators into `{ value, done }` syntax
418+
this._mongooseOptions._asyncIterator = true;
419+
return this;
438420
};
439421
}
440422

441-
/*!
442-
* ignore
443-
*/
444-
445-
function _transformForAsyncIterator(doc) {
446-
return doc == null ? { done: true } : { value: doc, done: false };
447-
}
448-
449423
/**
450424
* Get the next doc from the underlying cursor and mongooseify it
451425
* (populate, etc.)
@@ -456,16 +430,38 @@ function _transformForAsyncIterator(doc) {
456430

457431
function _next(ctx, cb) {
458432
let callback = cb;
459-
if (ctx._transforms.length) {
460-
callback = function(err, doc) {
461-
if (err || (doc === null && !ctx._mongooseOptions.transformNull)) {
462-
return cb(err, doc);
433+
434+
// Create a custom callback to handle transforms, async iterator, and transformNull
435+
callback = function(err, doc) {
436+
if (err) {
437+
return cb(err);
438+
}
439+
440+
// Handle null documents - if asyncIterator, we need to return `done: true`, otherwise just
441+
// skip. In either case, avoid transforms.
442+
if (doc === null) {
443+
if (ctx._mongooseOptions._asyncIterator) {
444+
return cb(null, { done: true });
445+
} else {
446+
return cb(null, null);
463447
}
464-
cb(err, ctx._transforms.reduce(function(doc, fn) {
448+
}
449+
450+
// Apply transforms
451+
if (ctx._transforms.length && doc !== null) {
452+
doc = ctx._transforms.reduce(function(doc, fn) {
465453
return fn.call(ctx, doc);
466-
}, doc));
467-
};
468-
}
454+
}, doc);
455+
}
456+
457+
// This option is set in `Symbol.asyncIterator` code paths.
458+
// For async iterator, we need to convert to {value, done} format
459+
if (ctx._mongooseOptions._asyncIterator) {
460+
return cb(null, { value: doc, done: false });
461+
}
462+
463+
return cb(null, doc);
464+
};
469465

470466
if (ctx._error) {
471467
return immediate(function() {

lib/query.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5476,8 +5476,10 @@ Query.prototype.nearSphere = function() {
54765476
*/
54775477

54785478
if (Symbol.asyncIterator != null) {
5479-
Query.prototype[Symbol.asyncIterator] = function() {
5480-
return this.cursor().transformNull()._transformForAsyncIterator();
5479+
Query.prototype[Symbol.asyncIterator] = function queryAsyncIterator() {
5480+
// Set so QueryCursor knows it should transform results for async iterators into `{ value, done }` syntax
5481+
this._mongooseOptions._asyncIterator = true;
5482+
return this.cursor();
54815483
};
54825484
}
54835485

test/query.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2354,6 +2354,19 @@ describe('Query', function() {
23542354
assert.strictEqual(called, 1);
23552355
});
23562356

2357+
it('transform with for/await and cursor', async function() {
2358+
const Model = db.model('Test', new Schema({ name: String }));
2359+
2360+
await Model.create({ name: 'test' });
2361+
const cursor = Model.find().transform(doc => doc.name.toUpperCase()).cursor();
2362+
const names = [];
2363+
for await (const name of cursor) {
2364+
names.push(name);
2365+
}
2366+
2367+
assert.deepStrictEqual(names, ['TEST']);
2368+
});
2369+
23572370
describe('orFail (gh-6841)', function() {
23582371
let Model;
23592372

0 commit comments

Comments
 (0)