@gravito/nebula-s3
v1.0.1
Published
S3 storage driver for @gravito/nebula
Maintainers
Readme
@gravito/nebula-s3
S3 儲存驅動,適用於 @gravito/nebula。
支援 AWS S3、Cloudflare R2、MinIO 等 S3 相容服務。
安裝
bun add @gravito/nebula-s3功能特點
- ✅ 完整的 StorageStore 實作
- ✅ Stream 支援 - putStream/getStream
- ✅ 分頁列舉 - listPaginated
- ✅ Metadata 支援 - customMetadata, setMetadata
- ✅ Presigned URL - getSignedUrl
- ✅ 多服務支援 - AWS S3, Cloudflare R2, MinIO
快速開始
AWS S3
import { S3Store } from '@gravito/nebula-s3'
import { StorageManager } from '@gravito/nebula'
const s3Store = new S3Store({
bucket: 'my-bucket',
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
})
const storage = new StorageManager(
(name) => s3Store,
{ default: 's3' }
)
// 使用
await storage.put('file.txt', 'Hello, S3!')
const data = await storage.get('file.txt')Cloudflare R2
const r2Store = new S3Store({
bucket: 'my-bucket',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
region: 'auto',
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
})MinIO
const minioStore = new S3Store({
bucket: 'my-bucket',
endpoint: 'http://localhost:9000',
region: 'us-east-1',
forcePathStyle: true, // MinIO 需要
credentials: {
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin',
},
})API
基本操作
// 上傳檔案
await s3Store.put('file.txt', 'content')
// 下載檔案
const data = await s3Store.get('file.txt')
// 刪除檔案
await s3Store.delete('file.txt')
// 檢查存在
const exists = await s3Store.exists('file.txt')
// 複製檔案
await s3Store.copy('source.txt', 'dest.txt')
// 移動檔案
await s3Store.move('old.txt', 'new.txt')串流操作
// 串流上傳(適合大檔案)
const fileStream = Bun.file('large-video.mp4').stream()
await s3Store.putStream('videos/upload.mp4', fileStream)
// 串流下載
const downloadStream = await s3Store.getStream('videos/upload.mp4')
if (downloadStream) {
const file = Bun.file('downloaded.mp4')
const writer = file.writer()
const reader = downloadStream.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
writer.write(value)
}
await writer.end()
}Metadata 操作
// 上傳時設定 metadata
await s3Store.put('image.jpg', imageData, {
contentType: 'image/jpeg',
metadata: {
author: 'John Doe',
tags: 'nature,sunset',
},
cacheControl: 'public, max-age=31536000',
})
// 讀取 metadata
const meta = await s3Store.getMetadata('image.jpg')
console.log(meta.customMetadata?.author) // "John Doe"
// 更新 metadata
await s3Store.setMetadata('image.jpg', {
tags: 'nature,sunset,photography',
})分頁列舉
// 列舉檔案(分頁)
const page1 = await s3Store.listPaginated('uploads/', {
maxResults: 100,
})
console.log(`Found ${page1.count} files`)
// 繼續獲取下一頁
if (page1.hasMore) {
const page2 = await s3Store.listPaginated('uploads/', {
maxResults: 100,
cursor: page1.nextCursor!,
})
}
// 完整分頁迭代
let cursor: string | null = null
do {
const result = await s3Store.listPaginated('uploads/', {
maxResults: 1000,
cursor: cursor ?? undefined,
})
for (const item of result.items) {
console.log(`File: ${item.key}, Size: ${item.size}`)
}
cursor = result.nextCursor
} while (cursor !== null)Presigned URL
// 產生 24 小時有效的下載連結
const url = await s3Store.getSignedUrl('private-file.pdf', 86400)
console.log(url)
// https://my-bucket.s3.us-east-1.amazonaws.com/private-file.pdf?X-Amz-Algorithm=...CDN 整合
const s3Store = new S3Store({
bucket: 'my-bucket',
region: 'us-east-1',
publicUrl: 'https://cdn.example.com', // CDN URL
credentials: { ... },
})
// 產生 CDN URL
const url = s3Store.getUrl('image.jpg')
// https://cdn.example.com/image.jpg配置選項
interface S3StoreOptions {
/** S3 Bucket 名稱 */
bucket: string
/** AWS Region (預設: 'auto') */
region?: string
/** 自定義 Endpoint (用於 MinIO, Cloudflare R2 等) */
endpoint?: string
/** AWS 憑證 */
credentials?: {
accessKeyId: string
secretAccessKey: string
}
/** 公開 URL 前綴 (用於 CDN) */
publicUrl?: string
/** 是否強制使用 path-style URL (MinIO 需要) */
forcePathStyle?: boolean
}效能優化建議
大檔案上傳
對於大型檔案(>100MB),建議使用 putStream 而非 put:
// ❌ 不佳 - 整個檔案載入記憶體
const file = await Bun.file('large.mp4').arrayBuffer()
await s3Store.put('videos/large.mp4', Buffer.from(file))
// ✅ 良好 - 串流上傳
const stream = Bun.file('large.mp4').stream()
await s3Store.putStream('videos/large.mp4', stream)批次操作
列舉大量檔案時,使用 listPaginated 並設定合理的 maxResults:
// ✅ 建議:分頁列舉
const result = await s3Store.listPaginated('uploads/', {
maxResults: 1000, // 建議 100-1000
})相容性
- AWS S3 - ✅ 完整支援
- Cloudflare R2 - ✅ 完整支援
- MinIO - ✅ 完整支援(需設定
forcePathStyle: true) - 其他 S3 相容服務 - ✅ 應該可正常運作
授權
MIT
相關連結
- @gravito/nebula - 核心儲存抽象層
- AWS SDK for JavaScript v3
