# S3 API

S3 兼容对象存储辅助能力位于 `ptool.s3` 和 `p.s3` 下。

## ptool.s3.connect

> `Unreleased` - 引入。

`ptool.s3.connect(options)` 打开一个 S3 兼容对象存储连接，并返回一个 `Connection` 对象。

`options` 字段：

- `bucket`（string，必填）：bucket 名称。
- `region`（string，选填）：AWS 区域或服务提供商区域。
- `endpoint`（string，选填）：自定义的 S3 兼容 endpoint URL，例如 MinIO、R2 或其他对象存储服务。
- `access_key_id`（string，选填）：访问密钥 ID。
- `secret_access_key`（string，选填）：访问密钥 secret。
- `session_token`（string，选填）：会话令牌。
- `root`（string，选填）：应用到所有对象操作的根前缀。
- `allow_anonymous`（boolean，选填）：当为 `true` 时，如果未配置凭证，则允许未签名请求。默认为 `false`。

环境变量回退：

- 显式传入的 `options` 值优先。
- 缺失的 `region`、`endpoint`、`access_key_id`、`secret_access_key` 和 `session_token` 会回退到：
  - `AWS_REGION`
  - `AWS_ENDPOINT`、`AWS_ENDPOINT_URL` 或 `AWS_S3_ENDPOINT`
  - `AWS_ACCESS_KEY_ID`
  - `AWS_SECRET_ACCESS_KEY`
  - `AWS_SESSION_TOKEN`
- 环境变量回退使用 `ptool` 的运行时环境视图，因此通过 `p.os.setenv(...)` 设置的值也会被 `ptool.s3.connect(...)` 看到。

示例：

```lua
local s3 = ptool.s3.connect({
  bucket = "artifacts",
  region = "auto",
  endpoint = "https://<account>.r2.cloudflarestorage.com",
  access_key_id = p.os.getenv("AWS_ACCESS_KEY_ID"),
  secret_access_key = p.os.getenv("AWS_SECRET_ACCESS_KEY"),
  root = "builds/",
})
```

## Connection

> `Unreleased` - 引入。

`Connection` 表示由 `ptool.s3.connect()` 返回的已打开对象存储连接。

它实现为 Lua userdata。

方法：

- `conn:read(path)` -> `string`
- `conn:write(path, content)` -> `nil`
- `conn:delete(path)` -> `nil`
- `conn:exists(path)` -> `boolean`
- `conn:list([prefix])` -> `table`
- `conn:stat(path)` -> `table`

路径规则：

- 除非另有说明，对象路径必须是非空字符串。
- 前导 `/` 会被忽略，因此 `/foo/bar.txt` 和 `foo/bar.txt` 指向同一个对象。
- 当连接配置了 `root` 时，路径会相对于 `root` 解析。

条目 table 结构：

- `path`（string）：相对于连接根路径的对象路径。
- `size`（integer）：对象大小，单位为字节。
- `etag`（string | nil）：对象的 ETag（如果可用）。
- `last_modified`（string | nil）：最后修改时间戳（如果可用）。
- `content_type`（string | nil）：对象内容类型（如果可用）。
- `is_file`（boolean）：该条目是否为文件。
- `is_dir`（boolean）：该条目是否为目录。
- `mode`（string）：`"file"`、`"dir"` 或 `"unknown"` 之一。

### read

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:read`。

`conn:read(path)` 以原始字节读取对象，并返回 Lua 字符串。

- `path`（string，必填）：对象路径。
- 返回：`string`。

示例：

```lua
local s3 = ptool.s3.connect({ bucket = "artifacts" })
local content = s3:read("releases/v1.0.0/notes.txt")
print(content)
```

### write

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:write`。

`conn:write(path, content)` 将 Lua 字符串作为原始字节写入对象。

- `path`（string，必填）：对象路径。
- `content`（string，必填）：要上传的字节内容。

行为：

- `content` 会按字节原样上传。
- 嵌入的 NUL 字节和非 UTF-8 字节会被保留。

示例：

```lua
local s3 = ptool.s3.connect({ bucket = "artifacts" })
s3:write("tmp/hello.txt", "hello\n")
s3:write("tmp/blob.bin", "\x00\xffABC")
```

### delete

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:delete`。

`conn:delete(path)` 删除一个对象。

- `path`（string，必填）：对象路径。

### exists

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:exists`。

`conn:exists(path)` 检查对象是否存在。

- `path`（string，必填）：对象路径。
- 返回：`boolean`。

### list

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:list`。

`conn:list([prefix])` 列出某个前缀下的条目，并返回致密 Lua 数组表。

- `prefix`（string，选填）：要列出的前缀。默认为连接根路径。
- 返回：`table`。

示例：

```lua
local s3 = ptool.s3.connect({ bucket = "artifacts", root = "builds/" })
local entries = s3:list("2026/")

for _, entry in ipairs(entries) do
  print(entry.path, entry.mode, entry.size)
end
```

### stat

> `Unreleased` - 引入。

规范 API 名称：`ptool.s3.Connection:stat`。

`conn:stat(path)` 返回单个对象的元数据。

- `path`（string，必填）：对象路径。
- 返回：`table`。

示例：

```lua
local s3 = ptool.s3.connect({ bucket = "artifacts" })
local meta = s3:stat("releases/v1.0.0/app.tar.zst")
print(meta.size, meta.etag, meta.last_modified)
```
