2022-10-24 17:11:59 +02:00
import type {
2022-10-13 11:01:35 +02:00
IDestinationProvider ,
ISourceProvider ,
ITransferEngine ,
ITransferEngineOptions ,
2022-11-08 14:26:48 +01:00
ITransferResults ,
2022-10-13 11:01:35 +02:00
} from '../../types' ;
2022-11-08 14:26:48 +01:00
class TransferEngine <
S extends ISourceProvider = ISourceProvider ,
D extends IDestinationProvider = IDestinationProvider
> implements ITransferEngine
{
2022-10-13 11:01:35 +02:00
sourceProvider : ISourceProvider ;
destinationProvider : IDestinationProvider ;
options : ITransferEngineOptions ;
constructor (
sourceProvider : ISourceProvider ,
destinationProvider : IDestinationProvider ,
options : ITransferEngineOptions
) {
this . sourceProvider = sourceProvider ;
this . destinationProvider = destinationProvider ;
this . options = options ;
}
private assertStrapiVersionIntegrity ( sourceVersion? : string , destinationVersion? : string ) {
const strategy = this . options . versionMatching ;
if ( ! sourceVersion || ! destinationVersion ) {
return ;
}
if ( strategy === 'ignore' ) {
return ;
}
if ( strategy === 'exact' && sourceVersion === destinationVersion ) {
return ;
}
const sourceTokens = sourceVersion . split ( '.' ) ;
const destinationTokens = destinationVersion . split ( '.' ) ;
const [ major , minor , patch ] = sourceTokens . map (
( value , index ) = > value === destinationTokens [ index ]
) ;
if (
( strategy === 'major' && major ) ||
( strategy === 'minor' && major && minor ) ||
( strategy === 'patch' && major && minor && patch )
) {
return ;
}
throw new Error (
` Strapi versions doesn't match ( ${ strategy } check): ${ sourceVersion } does not match with ${ destinationVersion } `
) ;
}
async boostrap ( ) : Promise < void > {
await Promise . all ( [
// bootstrap source provider
this . sourceProvider . bootstrap ? . ( ) ,
// bootstrap destination provider
this . destinationProvider . bootstrap ? . ( ) ,
] ) ;
}
async close ( ) : Promise < void > {
await Promise . all ( [
// close source provider
this . sourceProvider . close ? . ( ) ,
// close destination provider
this . destinationProvider . close ? . ( ) ,
] ) ;
}
async integrityCheck ( ) : Promise < boolean > {
const sourceMetadata = await this . sourceProvider . getMetadata ( ) ;
const destinationMetadata = await this . destinationProvider . getMetadata ( ) ;
if ( ! sourceMetadata || ! destinationMetadata ) {
return true ;
}
try {
// Version check
this . assertStrapiVersionIntegrity (
sourceMetadata ? . strapi ? . version ,
destinationMetadata ? . strapi ? . version
) ;
return true ;
} catch ( error ) {
if ( error instanceof Error ) {
console . error ( 'Integrity checks failed:' , error . message ) ;
}
return false ;
}
}
2022-11-08 14:26:48 +01:00
async transfer ( ) : Promise < ITransferResults < S , D > > {
2022-10-13 11:01:35 +02:00
try {
await this . boostrap ( ) ;
const isValidTransfer = await this . integrityCheck ( ) ;
if ( ! isValidTransfer ) {
throw new Error (
` Unable to transfer the data between ${ this . sourceProvider . name } and ${ this . destinationProvider . name } . \ nPlease refer to the log above for more information. `
) ;
}
2022-11-03 10:12:16 +02:00
await this . transferSchemas ( ) ;
2022-11-08 15:23:14 +01:00
await this . transferEntities ( ) ;
2022-10-13 11:01:35 +02:00
await this . transferMedia ( ) ;
await this . transferLinks ( ) ;
await this . transferConfiguration ( ) ;
await this . close ( ) ;
2022-11-08 15:23:14 +01:00
} catch ( e : any ) {
throw new Error ( e ) ;
2022-10-13 11:01:35 +02:00
// Rollback the destination provider if an exception is thrown during the transfer
// Note: This will be configurable in the future
// await this.destinationProvider?.rollback(e);
}
2022-11-08 14:26:48 +01:00
return {
source : this.sourceProvider.results ,
destination : this.destinationProvider.results ,
} ;
2022-10-13 11:01:35 +02:00
}
2022-11-03 10:12:16 +02:00
async transferSchemas ( ) : Promise < void > {
const inStream = await this . sourceProvider . streamSchemas ? . ( ) ;
const outStream = await this . destinationProvider . getSchemasStream ? . ( ) ;
2022-11-07 11:10:05 +02:00
2022-11-03 10:12:16 +02:00
if ( ! inStream || ! outStream ) {
throw new Error ( 'Unable to transfer schemas, one of the streams is missing' ) ;
}
return new Promise ( ( resolve , reject ) = > {
inStream
// Throw on error in the source
. on ( 'error' , reject ) ;
outStream
// Throw on error in the destination
. on ( 'error' , ( e ) = > {
reject ( e ) ;
} )
// Resolve the promise when the destination has finished reading all the data from the source
. on ( 'close' , resolve ) ;
inStream . pipe ( outStream ) ;
} ) ;
}
2022-10-13 11:01:35 +02:00
async transferEntities ( ) : Promise < void > {
2022-10-24 17:11:59 +02:00
const inStream = await this . sourceProvider . streamEntities ? . ( ) ;
const outStream = await this . destinationProvider . getEntitiesStream ? . ( ) ;
2022-10-31 10:21:23 +01:00
if ( ! inStream ) {
throw new Error ( 'Unable to transfer entities, source stream is missing' ) ;
}
if ( ! outStream ) {
throw new Error ( 'Unable to transfer entities, destination stream is missing' ) ;
2022-10-24 17:11:59 +02:00
}
return new Promise ( ( resolve , reject ) = > {
inStream
// Throw on error in the source
. on ( 'error' , ( e ) = > {
reject ( e ) ;
} ) ;
outStream
// Throw on error in the destination
. on ( 'error' , ( e ) = > {
reject ( e ) ;
} )
// Resolve the promise when the destination has finished reading all the data from the source
. on ( 'close' , resolve ) ;
inStream . pipe ( outStream ) ;
} ) ;
2022-10-13 11:01:35 +02:00
}
async transferLinks ( ) : Promise < void > {
2022-10-24 17:11:59 +02:00
const inStream = await this . sourceProvider . streamLinks ? . ( ) ;
const outStream = await this . destinationProvider . getLinksStream ? . ( ) ;
2022-10-31 10:21:23 +01:00
if ( ! inStream ) {
throw new Error ( 'Unable to transfer links, source stream is missing' ) ;
}
if ( ! outStream ) {
throw new Error ( 'Unable to transfer links, destination stream is missing' ) ;
2022-10-24 17:11:59 +02:00
}
return new Promise ( ( resolve , reject ) = > {
inStream
// Throw on error in the source
. on ( 'error' , reject ) ;
outStream
// Throw on error in the destination
. on ( 'error' , reject )
// Resolve the promise when the destination has finished reading all the data from the source
. on ( 'close' , resolve ) ;
inStream . pipe ( outStream ) ;
} ) ;
2022-10-13 11:01:35 +02:00
}
async transferMedia ( ) : Promise < void > {
console . log ( 'transferMedia not yet implemented' ) ;
return new Promise ( ( resolve ) = > resolve ( ) ) ;
}
async transferConfiguration ( ) : Promise < void > {
2022-11-02 17:26:32 +01:00
const inStream = await this . sourceProvider . streamConfiguration ? . ( ) ;
const outStream = await this . destinationProvider . getConfigurationStream ? . ( ) ;
if ( ! inStream ) {
throw new Error ( 'Unable to transfer configuration, source stream is missing' ) ;
}
if ( ! outStream ) {
throw new Error ( 'Unable to transfer configuration, destination stream is missing' ) ;
}
return new Promise ( ( resolve , reject ) = > {
inStream
// Throw on error in the source
. on ( 'error' , reject ) ;
outStream
// Throw on error in the destination
. on ( 'error' , reject )
// Resolve the promise when the destination has finished reading all the data from the source
. on ( 'close' , resolve ) ;
inStream . pipe ( outStream ) ;
} ) ;
2022-10-13 11:01:35 +02:00
}
}
2022-10-13 16:52:16 +02:00
2022-11-08 14:26:48 +01:00
export const createTransferEngine = < S extends ISourceProvider , D extends IDestinationProvider > (
sourceProvider : S ,
destinationProvider : D ,
2022-10-13 16:52:16 +02:00
options : ITransferEngineOptions
2022-11-08 14:26:48 +01:00
) : TransferEngine < S , D > = > {
return new TransferEngine < S , D > ( sourceProvider , destinationProvider , options ) ;
2022-10-13 16:52:16 +02:00
} ;