S3 对象存储
通过统一的 s3mini API 在本地存储、远端 S3 和平台实现之间切换。
@pluxel/storage目前只供 Pluxel 工作区使用,尚不是公开安装入口。完整边界见 Package 矩阵。
@pluxel/storage 提供接近原始 bucket 的 S3 能力。业务 Plugin 通过 S3.bucket(id).client 使用 s3mini 1.x API,宿主则通过 S3Plugin 配置一个有界的 local/remote bucket catalog。
写入与读取对象
import { } from '@pluxel/storage'
import { , } from '@pluxel/runtime'
@({ : 'Assets' })
export class extends {
constructor(private readonly : ) {
super()
}
(: string, : Blob) {
return this.
.bucket('assets')
.client.putAnyObject(`avatars/${}.webp`, , 'image/webp')
}
(: string) {
return this..bucket('assets').client.getObjectResponse(`avatars/${}.webp`)
}
}默认 local backend
没有配置时,S3Plugin 使用:
{
buckets: [
{
id: 'default',
backend: {
type: 'local',
rootDir: '.pluxel/s3',
bucketName: 'local',
syncWrites: true,
},
},
],
}显式配置示例:
以下 host 是 测试宿主,用于验证装配。应用入口按 添加插件 配置清单、配置记录和自动启动。
import { S3Plugin } from '@pluxel/storage'
await host.start(S3Plugin, {
initialConfig: {
buckets: [
{
id: 'assets',
backend: {
type: 'local',
rootDir: '/var/lib/my-app/s3',
bucketName: 'assets',
syncWrites: true,
},
},
],
},
})
await host.start(AssetsPlugin)local backend 支持 CRUD、typed/stream/range/conditional reads、delimiter/prefix listing、opaque pagination、copy/move、批量删除和显式 multipart upload。putAnyObject() 会流式写入临时 container,校验声明长度,计算 SHA-256 ETag,按需 fsync,最后 atomic rename;失败的写入不会发布成完整对象。
完整 S3 key 会被可逆 base32 编码并拆成有界文件名。../x、leading slash、反斜杠、空 path segment 和 Unicode 都只是普通 object key,不参与本地路径解析。
.pluxel/s3 适合开发。生产若使用 local backend,应把 rootDir 放在 immutable dist/ 之外,并纳入备份、磁盘容量、权限和故障恢复策略。
S3Client 是 Omit<S3mini, `_${string}`>,因此公开业务面直接锚定 s3mini:bucket create/exists、listing、字符串/JSON/ArrayBuffer/Response 读取、range 与 conditional read、PUT、multipart、copy/move、delete 和 presign 都使用原生 method signature。
它是 raw bucket capability,不会自动给 key 加 caller prefix。key namespace、metadata schema、对象大小与 content type policy、保留期和删除所有权都由 consumer 或 host contract 定义。
先调用 putAvatar() 写入一个小图片,再调用 getAvatar() 确认字节与 Content-Type。示例选择的 bucket ID 是 assets;默认配置只有 default,因此不能省略上面的显式 bucket 配置。
远端 S3:anonymous
同一个 provider 可以创建真实 S3mini client:
await host.start(S3Plugin, {
initialConfig: {
buckets: [
{
id: 'public-assets',
backend: {
type: 'remote',
endpoint: 'https://public-assets.s3.example.com',
region: 'auto',
credentials: { type: 'anonymous' },
requestSizeInBytes: 8 * 1024 * 1024,
requestAbortTimeout: 30_000,
minPartSize: 8 * 1024 * 1024,
},
},
],
},
})endpoint 必须是没有 userinfo、query 或 hash 的 HTTP(S) URL。minPartSize 至少 5 MiB、最多 5 GiB;其余 size/timeout 字段必须是正整数。
anonymous 是显式选择,不是 credential 查找失败后的隐式 fallback。
远端 S3:Vault credential
access key 不进入普通 Plugin config。先把以下对象写入 Vault:
import type { S3AccessKeyCredentials } from '@pluxel/storage'
const credentials = {
accessKeyId: '…',
secretAccessKey: '…',
} satisfies S3AccessKeyCredentials然后在 S3 config 中只保存引用:
await host.start(S3Plugin, {
initialConfig: {
buckets: [
{
id: 'assets',
backend: {
type: 'remote',
endpoint: 'https://assets.s3.us-east-1.amazonaws.com',
region: 'us-east-1',
credentials: {
type: 'vault',
namespace: 'production-secrets',
key: 'assets.s3',
},
},
},
],
},
})省略 namespace 时使用当前 S3Plugin 的 Vault namespace;default bucket 的 key 默认是 s3.credentials,其他 bucket 默认是
s3.<bucket-id>.credentials。provider 在 init() 中为每个 remote/vault bucket 读取一次 credential snapshot。Vault 不可用、key
缺失或对象非法分别以 S3CredentialsError.reason 的 unavailable、missing、invalid 失败整个 generation。
Workbench enabled 时,provider 固定发布一个 S3 buckets Content,以 bounded rows 显示各 ID 的 local、remote/anonymous 或
remote/vault;只有选中的 remote/vault bucket 接受一次性 password form,local/anonymous 报告 not-applicable、明确拒绝且
不访问 Vault。Handler 将 replacement access key 写入当前配置引用的 Vault record,
并重新检查 authenticated Management principal、当前 generation 与 backend,
串行执行写入和 flush()。Content 不读取或展示旧 credential,新 credential 也不会进入 plan、load、action result 或日志。
保存后仍需通过正常 Plugin management restart 当前 S3 generation,新的 client 才会读取 replacement。
这条 Content 不能用于首次 provisioning。缺失或非法 record 会让 S3Plugin 启动失败,而失败 generation 的 Workbench publication 必然回滚;宿主必须在启动前写入 credential。不要为了显示 setup Content 让 S3 capability 半启动,也不要另建脱离 Plugin owner lifecycle 的 secret registry。
local 和 anonymous backend 不访问 Vault。当前 s3mini contract 不接受 session token;STS、平台 credential chain 或特殊认证协议需要由平台提供另一个 S3 implementation。
local 与 remote 的能力差异
remote backend 是真实 s3mini client,支持范围取决于目标 S3 服务。local backend 对无法保持语义的操作会明确抛出 S3UnsupportedOperationError,其稳定 code 为 S3_UNSUPPORTED_OPERATION,包括:
- presigned URL;
- bucket/object versioning 相关操作;
- SSE-C;
- version-specific copy/delete;
- replacement tagging。
不要通过捕获并忽略该错误来假装操作成功。若业务必须依赖 presign 或 versioning,应在部署契约中要求 remote/platform provider,或显式检测并返回 capability unavailable。
multipart 与大对象
普通 putAnyObject() 已支持流式 body。需要显式控制 multipart 时,使用 s3mini 的原生序列:
const client = this.s3.bucket('assets').client
const uploadId = await client.getMultipartUploadId(
'exports/archive.bin',
'application/octet-stream',
)
try {
const first = await client.uploadPart('exports/archive.bin', uploadId, firstChunk, 1)
const second = await client.uploadPart('exports/archive.bin', uploadId, secondChunk, 2)
await client.completeMultipartUpload('exports/archive.bin', uploadId, [first, second])
} catch (error) {
await client.abortMultipartUpload('exports/archive.bin', uploadId)
throw error
}firstChunk 与 secondChunk 应使用 uploadPart() 接受的 s3mini body 类型。part number、最小 part size 与服务限制仍由 s3mini/backend 约束。
Catalog 生命周期与一致性
S3Plugin 的 buckets 配置包含 1–64 个唯一 stable ID。consumer 通过 s3.bucket(id) 取得 owner-bound handle;重复选择为
O(1),consumer 或 provider 停止后旧 handle 会被撤销。所有 bucket 属于同一个原子 generation:资源并行初始化,任一项失败会回滚
全部项。需要独立启停、故障隔离或扩缩容的对象存储应使用独立进程/host。
provider stop/replacement 后,旧 S3 caller facade 先由 Core generation gate 拒绝。此前取得的 local client
会被 revoke 并抛 S3NotRunningError(code S3_NOT_RUNNING);remote client 的 in-flight fetch 会收到 lifecycle abort。
对象存储不是关系型事务。若数据库 metadata 与对象必须协调,先设计显式 state machine,再用 outbox、幂等 key 和补偿流程处理对象 side effect;不要假设 DB transaction 能回滚 S3 PUT。
最后更新于