File storage models, the platform and recorder ports, and the service facade
async test "记录是意图加存储回填的字段,不是调用方自己拼的字符串" {
let draft = @model.FileInfo::draft("local-1", "uploads", "report", "pdf")
assert_eq(draft.url, "")
assert_eq(draft.original_filename, "report")
let stamped = draft.stamped(@model.Stamp::{
platform: None,
path: Some("uploads/2026/10/"),
url: Some("https://files.example.invalid/uploads/2026/10/report.pdf"),
size: Some(4096L),
create_time: Some(1760000000000L),
upload_id: None,
upload_status: None,
user_metadata: None,
})
assert_eq(stamped.ext, "pdf")
assert_eq(stamped.size, Some(4096L))
// 空着的 `platform` 沿用原值:一次只改想改的那几格。
assert_eq(stamped.platform, "local-1")
let keyed = @model.object_key("uploads", "a/b.txt")
assert_eq(keyed, "uploads/a/b.txt")
// ACL 只有三档,参考件 x-file-storage 那种"不认识的 ACL 返回 false"在这里构造不出来。
assert_eq(@model.CannedAcl::PublicRead.header_value(), "public-read")
assert_true(stamped != draft)
}moon add mldong/moon-file-storage
moon add moonbitlang/async
moon add mldong/moon-file-storage-local
moon add mldong/moon-file-storage-recorder-filemoon add mldong/moon-file-storage-s3 # 配 mldong/moon-s3
moon add mldong/moon-file-storage-qiniu # 配 mldong/moon-kodo
moon add mldong/moon-file-storage-oss # 配 mldong/moon-oss
moon add mldong/moon-file-storage-cos # 配 mldong/moon-cos
moon add mldong/moon-file-storage-obs # 配 mldong/moon-obs
moon add mldong/moon-file-storage-ftp # 载体 PaiGack/ftp(依赖自动进)import {
"mldong/moon-file-storage/model",
"mldong/moon-file-storage/platform",
"mldong/moon-file-storage/storage",
"mldong/moon-file-storage-local/local",
"mldong/moon-file-storage-recorder-file/file" @record,
"moonbitlang/async",
"moonbitlang/async/fs",
"moonbitlang/core/encoding/utf8",
}async test "装上本机后端与记录器,把一次上传跑通" {
let root = @fs.tmpdir(prefix="mfs-readme") catch {
err => abort("tmpdir: " + err.to_string())
}
let local_platform = @local.LocalPlatform::new(@local.LocalConfig::{
platform: "local-1",
root_dir: root,
domain: "https://files.example.invalid",
base_path: "uploads",
})
let recorder = @record.FileRecorder::new(@record.FileRecorderConfig::{
root_dir: root + "/records",
})
let platforms : Array[&@platform.Platform] = [local_platform]
let service = @storage.Service::new(platforms, recorder)
let intent = @model.FileInfo::draft("local-1", "uploads", "hello", "txt")
let source = @model.UploadSource::Bytes(@utf8.encode("hello from the readme"))
let saved = service.save(intent, source) catch {
_ => abort("save failed")
}
assert_true(saved.url != "")
assert_eq(service.exists(saved) catch { _ => false }, true)
assert_eq(service.delete(saved) catch { _ => false }, true)
service.close()
let _ = @fs.rmdir(root, recursive=true) catch { _ => () }
}Install
Download zipFile storage models, the platform and recorder ports, and the service facade