
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A unified NestJS storage library with S3 support, streaming upload, and configurable file retention
A unified NestJS storage library with S3 support, streaming upload, and configurable file retention.
import { StorageModule, StorageService } from 'sh-storage';
// 1. Configure in your module
@Module({
imports: [
StorageModule.forRoot({
config: {
provider: 's3',
s3: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
bucket: process.env.AWS_BUCKET,
},
},
}),
],
})
export class AppModule {}
// 2. Use in your service
@Injectable()
export class FileService {
constructor(private storage: StorageService) {}
async uploadFile(file: Express.Multer.File) {
return await this.storage.uploadBuffer(file.buffer, {
key: `uploads/${file.originalname}`,
contentType: file.mimetype,
});
}
}
# Using npm
npm install sh-storage
# Using yarn
yarn add sh-storage
# Using pnpm
pnpm add sh-storage
import { StorageModule } from 'sh-storage';
@Module({
imports: [
StorageModule.forRoot({
config: {
provider: 's3',
s3: {
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key',
region: 'us-east-1',
bucket: 'your-bucket-name',
},
prefix: 'mountseaapi', // 文件前缀,默认为 'mountseaapi'
retryConfig: {
maxRetries: 3, // 最大重试次数,默认3
retryDelay: 1000, // 重试间隔基数(毫秒),默认1000
},
},
}),
],
})
export class AppModule {}
import { Injectable } from '@nestjs/common';
import { StorageService } from 'sh-storage';
@Injectable()
export class FileService {
constructor(private readonly storageService: StorageService) {}
async uploadFile(file: Express.Multer.File) {
const result = await this.storageService.uploadBuffer(file.buffer, {
key: `uploads/${Date.now()}-${file.originalname}`,
contentType: file.mimetype,
});
return result;
}
async uploadStream(stream: NodeJS.ReadableStream, filename: string) {
const result = await this.storageService.uploadStream(stream, {
key: `videos/${Date.now()}-${filename}`,
contentType: 'video/mp4',
onProgress: (progress) => {
console.log(`Upload progress: ${progress.loaded}/${progress.total}`);
},
});
return result;
}
async downloadFile(key: string) {
return await this.storageService.download({ key });
}
async deleteFile(key: string) {
return await this.storageService.delete({ keys: [key] });
}
}
uploadStream(stream, options) - Upload a readable streamuploadBuffer(buffer, options) - Upload a bufferuploadString(content, options) - Upload a stringdownloadAndUpload(url, options, maxRetries?) - Download from URL and uploaddownloadAndUploadBatch(urls, keyPrefix, options, maxRetries?) - Batch download and uploaddownload(options) - Download as streamdownloadString(key) - Download as stringdelete(options) - Delete fileslist(options) - List filesgetFileInfo(key) - Get file informationexists(key) - Check if file existscleanupExpiredFiles(prefix?) - Clean up expired filesgeneratePresignedUrl(options) - Generate presigned URLinterface UploadOptions {
key: string; // File key/path
contentType?: string; // MIME type
metadata?: Record<string, string>; // Custom metadata
tags?: Record<string, string>; // Tags for organization
}
interface StreamUploadOptions extends UploadOptions {
onProgress?: (progress: { loaded: number; total?: number }) => void;
}
import { ConfigService } from '@nestjs/config';
import { StorageModule } from 'sh-storage';
@Module({
imports: [
StorageModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
provider: 's3',
s3: {
accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
region: configService.get('AWS_REGION'),
bucket: configService.get('AWS_BUCKET'),
},
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
// 自定义前缀和重试配置
const config = {
provider: 's3',
s3: { /* ... */ },
prefix: 'myapp', // 自定义前缀
retryConfig: {
maxRetries: 5, // 最大重试5次
retryDelay: 2000, // 重试间隔基数2秒
},
};
// 使用默认配置
const config = {
provider: 's3',
s3: { /* ... */ },
// prefix 默认为 'mountseaapi'
// retryConfig 默认为 { maxRetries: 3, retryDelay: 1000 }
};
async uploadWithProgress(file: Express.Multer.File) {
const stream = Readable.from(file.buffer);
return await this.storageService.uploadStream(stream, {
key: `uploads/${file.originalname}`,
contentType: file.mimetype,
onProgress: (progress) => {
const percentage = progress.total
? Math.round((progress.loaded / progress.total) * 100)
: 0;
console.log(`Upload progress: ${percentage}%`);
},
});
}
async processMultipleFiles(urls: string[]) {
const results = await this.storageService.downloadAndUploadBatch(
urls,
'batch-uploads',
{
contentType: 'application/octet-stream',
}
);
return results;
}
The library provides comprehensive error handling with detailed error messages:
try {
const result = await this.storageService.uploadBuffer(buffer, options);
} catch (error) {
if (error.message.includes('S3 upload failed')) {
// Handle S3 specific errors
} else if (error.message.includes('Local upload failed')) {
// Handle local storage errors
}
// Handle other errors
}
For S3 configuration, you can use these environment variables:
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
AWS_BUCKET=your-bucket-name
Current version: 1.0.1
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { StorageService } from 'sh-storage';
@Controller('files')
export class FileController {
constructor(private readonly storageService: StorageService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
const result = await this.storageService.uploadBuffer(file.buffer, {
key: `uploads/${Date.now()}-${file.originalname}`,
contentType: file.mimetype,
});
return {
success: true,
key: result.key,
url: result.url,
size: file.size,
};
}
}
import { Injectable } from '@nestjs/common';
import { StorageService } from 'sh-storage';
import { Readable } from 'stream';
@Injectable()
export class VideoService {
constructor(private readonly storageService: StorageService) {}
async processVideo(videoBuffer: Buffer, filename: string) {
const stream = Readable.from(videoBuffer);
return await this.storageService.uploadStream(stream, {
key: `videos/processed/${Date.now()}-${filename}`,
contentType: 'video/mp4',
onProgress: (progress) => {
console.log(`Video upload: ${progress.loaded}/${progress.total || 'unknown'}`);
},
});
}
}
MIT
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)FAQs
A unified NestJS storage library with S3 support, streaming upload, and configurable file retention
We found that sh-storage demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.