rong_s3

S3 兼容对象存储

本页 API 参考由仓库内的英文文档生成,暂仅提供英文版。

S3

S3-compatible object storage client. Exposed as Rong.S3Client.

Quick Start

const client = new Rong.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  endpoint: "https://s3.us-east-1.amazonaws.com",
});

await client.write("hello.txt", "Hello World!");

const file = client.file("hello.txt");
const text = await file.text();

await client.delete("hello.txt");

Rong.S3Client

Constructor

const client = new Rong.S3Client({
  accessKeyId: "...",
  secretAccessKey: "...",
  bucket: "my-bucket",
  region: "us-east-1",
  endpoint: "https://...",
  sessionToken: "...",
  acl: "public-read",
  virtualHostedStyle: true,
});

All constructor options are optional at construction time, but actual requests require accessKeyId, secretAccessKey, and bucket.

Rust hosts that own credentials can construct S3Client::new(config), optionally call .with_namespace(prefix) or .with_namespace_resolver(resolver), and inject it with client.into_js_object(ctx). That conversion automatically registers the client and internal S3File class without exposing either constructor. Rust-created clients always lock credentials, bucket, endpoint, and related configuration against JavaScript method-option overrides; a namespace controls only object-key isolation.

Use a fixed namespace when one client has one stable scope. Use a resolver when one injected client serves changing host-managed scopes. The resolver runs before every network-capable operation. file() remains lazy; its S3File references retain the resolver and re-resolve it when used, so caching a file reference cannot retain an earlier scope. Empty prefixes and resolver errors fail before bucket construction or network I/O.

Keep dynamic namespace state in Rust-owned JSContext::set_state/get_state rather than a JavaScript property:

struct NamespacePrefix(String);

ctx.set_state(NamespacePrefix("tenant-a/".to_owned()));
let client = S3Client::new(config).with_namespace_resolver(|ctx| {
    let namespace = ctx
        .get_state::<NamespacePrefix>()
        .ok_or_else(|| HostError::new("E_INVALID_STATE", "missing namespace context"))?;
    Ok(namespace.0.clone())
});

Injecting the returned object into ctx.host_namespace() exposes it as, for example, Rong.s3, without adding a separate top-level global.

Methods

client.file(path, options?)

Returns a lazy S3File reference. This does not perform any network request.

const file = client.file("data/report.json");
const file2 = client.file("other.txt", { bucket: "other-bucket" });

options uses the same config shape as the constructor and overrides the client config for that file handle.

await client.write(path, data, options?)

Upload data and return the number of bytes written.

await client.write("file.txt", "Hello World!");
await client.write("data.json", JSON.stringify(obj), {
  type: "application/json",
});

Accepted data types:

  • string
  • ArrayBuffer
  • Uint8Array

options.type overrides the content type. Other config fields in options override the client config for this request.

await client.delete(path) / await client.unlink(path)

Delete an object.

await client.delete("old-file.txt");

await client.exists(path)

Check whether an object exists.

const ok = await client.exists("config.json");

await client.size(path)

Get the object size in bytes.

const bytes = await client.size("large-file.bin");

await client.stat(path)

Get object metadata.

const stat = await client.stat("file.txt");
// {
//   etag: "\"abc123...\"",
//   lastModified: "...",
//   size: 1024,
//   type: "text/plain"
// }

await client.presign(path, options?)

Generate a presigned URL.

const getUrl = await client.presign("file.txt");
const putUrl = await client.presign("upload.bin", {
  expiresIn: 3600,
  method: "PUT",
});

Supported methods:

  • "GET"
  • "PUT"
  • "DELETE"

await client.list(options?)

List objects in the bucket.

const result = await client.list({
  prefix: "uploads/",
  maxKeys: 100,
  startAfter: "uploads/last-key",
});

// result.contents = [{ key, size, lastModified, etag? }, ...]
// result.isTruncated = boolean

list() currently fetches bucket pages from the backend and then applies startAfter and maxKeys on the returned results.

S3File

Lazy reference to an S3 object. Create it via client.file(). new S3File() is not allowed.

Reading

const file = client.file("data.json");

const text = await file.text();
const data = await file.json();
const buffer = await file.bytes();       // ArrayBuffer
const ab = await file.arrayBuffer();     // alias

Writing

await file.write("new content");
await file.write(JSON.stringify(data), { type: "application/json" });

Accepted write payloads:

  • string
  • ArrayBuffer
  • Uint8Array

Partial Reads

const first1k = file.slice(0, 1024);
const header = await first1k.text();

Metadata

file.name;             // "data.json"
file.size;             // NaN
await file.exists();   // boolean
await file.stat();     // { etag?, lastModified?, size, type? }

file.size is always NaN because S3 objects do not have synchronous size metadata; use await file.stat() or await client.size(path) instead.

Delete

await file.delete();
await file.unlink();

Presigned URL

const url = await file.presign({ expiresIn: 3600 });
const putUrl = await file.presign({ method: "PUT" });

Error Handling

try {
  await client.write("file.txt", data);
} catch (e) {
  // e.name === "S3Error"
}

Error codes:

  • ERR_S3_MISSING_CREDENTIALS
  • ERR_S3_INVALID_METHOD
  • ERR_S3