|
1 | | -import { Readable, Transform } from 'stream'; |
| 1 | +import { Readable } from 'stream'; |
2 | 2 |
|
3 | 3 | import { type BSONSerializeOptions, type Document, Long, pluckBSONSerializeOptions } from '../bson'; |
4 | 4 | import { type OnDemandDocumentDeserializeOptions } from '../cmap/wire_protocol/on_demand/document'; |
@@ -496,33 +496,10 @@ export abstract class AbstractCursor< |
496 | 496 | } |
497 | 497 |
|
498 | 498 | stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> { |
499 | | - if (options?.transform) { |
500 | | - const transform = options.transform; |
501 | | - const readable = new ReadableCursorStream(this); |
502 | | - |
503 | | - const transformedStream = readable.pipe( |
504 | | - new Transform({ |
505 | | - objectMode: true, |
506 | | - highWaterMark: 1, |
507 | | - transform(chunk, _, callback) { |
508 | | - try { |
509 | | - const transformed = transform(chunk); |
510 | | - callback(undefined, transformed); |
511 | | - } catch (err) { |
512 | | - callback(err); |
513 | | - } |
514 | | - } |
515 | | - }) |
516 | | - ); |
517 | | - |
518 | | - // Bubble errors to transformed stream, because otherwise no way |
519 | | - // to handle this error. |
520 | | - readable.on('error', err => transformedStream.emit('error', err)); |
521 | | - |
522 | | - return transformedStream; |
523 | | - } |
524 | | - |
525 | | - return new ReadableCursorStream(this); |
| 499 | + const transform = options?.transform ?? (doc => doc); |
| 500 | + return Readable.from(this, { autoDestroy: false, highWaterMark: 1, objectMode: true }).map( |
| 501 | + transform |
| 502 | + ); |
526 | 503 | } |
527 | 504 |
|
528 | 505 | async hasNext(): Promise<boolean> { |
@@ -1062,87 +1039,6 @@ export abstract class AbstractCursor< |
1062 | 1039 | } |
1063 | 1040 | } |
1064 | 1041 |
|
1065 | | -class ReadableCursorStream extends Readable { |
1066 | | - private _cursor: AbstractCursor; |
1067 | | - private _readInProgress = false; |
1068 | | - |
1069 | | - constructor(cursor: AbstractCursor) { |
1070 | | - super({ |
1071 | | - objectMode: true, |
1072 | | - autoDestroy: false, |
1073 | | - highWaterMark: 1 |
1074 | | - }); |
1075 | | - this._cursor = cursor; |
1076 | | - } |
1077 | | - |
1078 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars |
1079 | | - override _read(size: number): void { |
1080 | | - if (!this._readInProgress) { |
1081 | | - this._readInProgress = true; |
1082 | | - this._readNext(); |
1083 | | - } |
1084 | | - } |
1085 | | - |
1086 | | - override _destroy(error: Error | null, callback: (error?: Error | null) => void): void { |
1087 | | - this._cursor.close().then( |
1088 | | - () => callback(error), |
1089 | | - closeError => callback(closeError) |
1090 | | - ); |
1091 | | - } |
1092 | | - |
1093 | | - private _readNext() { |
1094 | | - if (this._cursor.id === Long.ZERO) { |
1095 | | - this.push(null); |
1096 | | - return; |
1097 | | - } |
1098 | | - |
1099 | | - this._cursor.next().then( |
1100 | | - result => { |
1101 | | - if (result == null) { |
1102 | | - this.push(null); |
1103 | | - } else if (this.destroyed) { |
1104 | | - this._cursor.close().then(undefined, squashError); |
1105 | | - } else { |
1106 | | - if (this.push(result)) { |
1107 | | - return this._readNext(); |
1108 | | - } |
1109 | | - |
1110 | | - this._readInProgress = false; |
1111 | | - } |
1112 | | - }, |
1113 | | - err => { |
1114 | | - // NOTE: This is questionable, but we have a test backing the behavior. It seems the |
1115 | | - // desired behavior is that a stream ends cleanly when a user explicitly closes |
1116 | | - // a client during iteration. Alternatively, we could do the "right" thing and |
1117 | | - // propagate the error message by removing this special case. |
1118 | | - if (err.message.match(/server is closed/)) { |
1119 | | - this._cursor.close().then(undefined, squashError); |
1120 | | - return this.push(null); |
1121 | | - } |
1122 | | - |
1123 | | - // NOTE: This is also perhaps questionable. The rationale here is that these errors tend |
1124 | | - // to be "operation was interrupted", where a cursor has been closed but there is an |
1125 | | - // active getMore in-flight. This used to check if the cursor was killed but once |
1126 | | - // that changed to happen in cleanup legitimate errors would not destroy the |
1127 | | - // stream. There are change streams test specifically test these cases. |
1128 | | - if (err.message.match(/operation was interrupted/)) { |
1129 | | - return this.push(null); |
1130 | | - } |
1131 | | - |
1132 | | - // NOTE: The two above checks on the message of the error will cause a null to be pushed |
1133 | | - // to the stream, thus closing the stream before the destroy call happens. This means |
1134 | | - // that either of those error messages on a change stream will not get a proper |
1135 | | - // 'error' event to be emitted (the error passed to destroy). Change stream resumability |
1136 | | - // relies on that error event to be emitted to create its new cursor and thus was not |
1137 | | - // working on 4.4 servers because the error emitted on failover was "interrupted at |
1138 | | - // shutdown" while on 5.0+ it is "The server is in quiesce mode and will shut down". |
1139 | | - // See NODE-4475. |
1140 | | - return this.destroy(err); |
1141 | | - } |
1142 | | - ); |
1143 | | - } |
1144 | | -} |
1145 | | - |
1146 | 1042 | configureResourceManagement(AbstractCursor.prototype); |
1147 | 1043 |
|
1148 | 1044 | /** |
|
0 commit comments