@@ -20,13 +20,15 @@ import type {
2020 RollbackDocument ,
2121 RollBackUpdateObject ,
2222} from './types' ;
23- import type { Collection , Document , ObjectId , WithId } from 'mongodb' ;
23+ import type { Collection , Document , Filter , ObjectId , WithId } from 'mongodb' ;
2424
2525const DEFAULT_BULK_SIZE = 5000 ;
2626const COUNT_TOO_LONG_WARNING_THRESHOLD_MS = 30000 ;
2727const COLLECTION_VALIDATION_LEVEL = 'moderate' ;
2828/** Fully delete collection, use with operation:DELETE_COLLECTION */
2929export const DELETE_COLLECTION = Symbol ( ) ;
30+ /** Fetches all documents excluding already rolled-back ones, use with query:FETCH_ALL */
31+ export const FETCH_ALL = Symbol ( ) ;
3032const defaultLogger = {
3133 info : ( ...args : unknown [ ] ) => {
3234 if ( process . env . NODE_ENV === 'test' ) {
@@ -43,8 +45,10 @@ const defaultLogger = {
4345 } ,
4446} ;
4547
46- export default class MongoBulkDataMigration < TSchema extends Document >
47- implements RollbackableUpdate
48+ export default class MongoBulkDataMigration <
49+ TSchema extends Document ,
50+ TQuery extends Filter < TSchema > | typeof FETCH_ALL = Filter < TSchema > ,
51+ > implements RollbackableUpdate
4852{
4953 private readonly options : DataMigrationOptions < TSchema > = {
5054 arrayFilters : [ ] ,
@@ -68,7 +72,12 @@ export default class MongoBulkDataMigration<TSchema extends Document>
6872 * @see <a href="/doc/softwareDesigns/bulkDataMigration/index.md">More information in the software design document</a>
6973 * @param config
7074 */
71- constructor ( config : DataMigrationConfig < TSchema > ) {
75+ constructor (
76+ ...args : [ keyof TQuery ] extends [ never ]
77+ ? [ 'Use FETCH_ALL instead of an empty query {}' ]
78+ : [ config : DataMigrationConfig < TSchema , TQuery > ]
79+ ) {
80+ const config = args [ 0 ] as DataMigrationConfig < TSchema , TQuery > ;
7281 this . id = config . id ;
7382 this . collectionName = config . collectionName ;
7483 Object . assign ( this . options , { ...config . options } ) ;
@@ -123,8 +132,10 @@ export default class MongoBulkDataMigration<TSchema extends Document>
123132 }
124133
125134 await this . lowerValidationLevel ( 'update' ) ;
126- const { cursor, totalEntries } =
127- await this . getCursorAndCount ( migrationCollection ) ;
135+ const { cursor, totalEntries } = await this . getCursorAndCount (
136+ migrationCollection ,
137+ rollbackCollection ,
138+ ) ;
128139 const formattedTotalEntries =
129140 totalEntries === NO_COUNT_AVAILABLE
130141 ? 'N/A (dontCount option ON)'
@@ -189,8 +200,13 @@ export default class MongoBulkDataMigration<TSchema extends Document>
189200 return bulkMigration . getResults ( ) ;
190201 }
191202
192- private async getCursorAndCount ( migrationCollection : Collection < TSchema > ) {
193- const cursor = getCursor ( this . migrationInfos ) ;
203+ private async getCursorAndCount (
204+ migrationCollection : Collection < TSchema > ,
205+ rollbackCollection : Collection < TSchema > ,
206+ ) {
207+ const resolvedQuery = await this . resolveQuery ( rollbackCollection ) ;
208+
209+ const cursor = getCursor ( resolvedQuery , this . migrationInfos ) ;
194210 const countTakingTooLongTimeout = setTimeout (
195211 ( ) =>
196212 this . logger . warn (
@@ -201,11 +217,14 @@ export default class MongoBulkDataMigration<TSchema extends Document>
201217 ) ;
202218 const totalEntries = this . options . dontCount
203219 ? NO_COUNT_AVAILABLE
204- : await getTotalEntries ( this . migrationInfos ) ;
220+ : await getTotalEntries ( resolvedQuery ) ;
205221 clearTimeout ( countTakingTooLongTimeout ) ;
206222 return { cursor, totalEntries } ;
207223
208- function getCursor ( { query, projection } : MigrationInfos < TSchema > ) {
224+ function getCursor (
225+ query : Filter < TSchema > | MongoPipeline ,
226+ { projection } : MigrationInfos < TSchema > ,
227+ ) {
209228 if ( isPipeline ( query ) ) {
210229 const pipelineWithProjection = query . concat (
211230 _ . isEmpty ( projection ) ? [ ] : [ { $project : projection } ] ,
@@ -215,7 +234,7 @@ export default class MongoBulkDataMigration<TSchema extends Document>
215234 return migrationCollection . find ( query , { projection } ) ;
216235 }
217236
218- async function getTotalEntries ( { query } : MigrationInfos < TSchema > ) {
237+ async function getTotalEntries ( query : Filter < TSchema > | MongoPipeline ) {
219238 if ( isPipeline ( query ) ) {
220239 const pipelineComputeTotal = query . concat ( { $count : 'totalEntries' } ) ;
221240 const cursorComputeTotal =
@@ -283,6 +302,23 @@ export default class MongoBulkDataMigration<TSchema extends Document>
283302 }
284303 }
285304
305+ private async resolveQuery (
306+ rollbackCollection : Collection < TSchema > ,
307+ ) : Promise < Filter < TSchema > | MongoPipeline > {
308+ if ( this . migrationInfos . query !== FETCH_ALL ) {
309+ return this . migrationInfos . query ;
310+ }
311+ const lastBackup = await rollbackCollection
312+ . find ( { } , { projection : { _id : 1 } } )
313+ . sort ( { _id : - 1 } )
314+ . limit ( 1 )
315+ . next ( ) ;
316+
317+ return lastBackup
318+ ? ( { _id : { $gt : lastBackup . _id } } as Filter < TSchema > )
319+ : { } ;
320+ }
321+
286322 async rollback ( ) : Promise < BulkOperationResult > {
287323 if ( ! this . options . rollbackable ) {
288324 this . logger . warn ( 'Calling rollback() on a non rollbackable script' ) ;
0 commit comments