# MuMu × AWS (S3) Plugin

A lightweight extension that adds **Amazon S3** capabilities to the MuMu runtime: credentials sessions, bucket creation, and object fetching (to file or as a streaming iterator). Built-in SigV4 signing, friendly error surfaces, and examples for both real S3 and S3‑compatible endpoints (e.g., LocalStack).

Language:**Rust**Runtime:**MuMu**Auth:**SigV4**Transport:**reqwest (blocking)**

**Table of contents**

1. [Highlights](#highlights)
2. [Quick start](#quick-start)
3. [Usage](#usage)
4. [API reference](#api-reference)
5. [How it works](#how-it-works)
6. [Repository layout](#repo-layout)
7. [LocalStack tips](#localstack)
8. [Troubleshooting](#troubleshooting)
9. [Security](#security)
10. [License](#license)
11. [Acknowledgements](#ack)

## Highlights

- 🔐 **Session‑based credentials** via aws:credentials(…) (*no* global state; returns a short session id)
- ✍️ **SigV4 signing** in pure Rust (src/functions/sigv4.rs)
- 🪣 **Create bucket**: aws:s3:create_bucket(…)
- ⬇️ **Fetch object**: save to file (path/folder) *or* stream as a MuMu **InkIterator**
- 🧯 **Robust errors**: dynamic calls catch panics and return [ok: false, error: "…"]

## Quick start

### 1) Build the plugin

The MuMu loader looks for a dynamic library named libmumu<base>.{so|dylib} (Linux/macOS) or <base>.dll (Windows). For this plugin, *base* is **aws**, so you’ll get libmumuaws.so, libmumuaws.dylib, or mumuaws.dll.

```bash
# from the repository root
cargo build --release

# point MuMu to the build directory (adjust for your OS/shell)
export MUMU_PLUGIN_PATH="$(pwd)/target/release"
```

### 2) Run MuMu (REPL or script)

```bash
# REPL (verbose is handy while developing)
mumu --verbose
# or (depending on install)
lava-mumu --verbose

# Run a .mu script
mumu examples/s3/fetch.mu
```

### 3) Load the plugin and configure credentials

```mu
extend("aws")
extend("process")  // to read environment variables

credentials = aws:credentials([
  accessKeyId:     process:env("AWS_ACCESS_KEY_ID"),
  secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
  region:          process:env("AWS_REGION"),
  // Optional: S3-compatible endpoint (e.g., LocalStack)
  // endpoint: "http://localhost:4566"
])
```

*Return value:* a short session id string — pass this to other AWS calls.

## Usage

### Create a bucket

```mu
extend("aws")
extend("process")
extend("test")  // just for test:diceware()

credentials = aws:credentials([
  accessKeyId:     process:env("AWS_ACCESS_KEY_ID"),
  secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
  region:          process:env("AWS_REGION"),
  endpoint:        "http://localhost:4566"  // optional
])

result = aws:s3:create_bucket([
  credentials: credentials,
  bucket:      test:diceware()
])

slog(result)  // → [ok: true, bucket: "..."] or [ok: false, error: "..."]
```

### Fetch an object — save to disk

```mu
extend("aws")
extend("process")

credentials = aws:credentials([
  accessKeyId:     process:env("AWS_ACCESS_KEY_ID"),
  secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
  region:          process:env("AWS_REGION")
])

result = aws:s3:fetch([
  credentials: credentials,
  bucket:      "my-bucket",
  key:         "path/to/object.png",
  // use either:
  // path: "/tmp/object.png",
  folder:      "/tmp/s3-downloads"  // filename inferred from key
])

slog(result)  // → [ok: true, path: "/tmp/s3-downloads/object.png"]
```

### Fetch an object — stream chunks

Omit path/folder to get an **InkIterator** yielding chunks: { chunk: IntArray }.

```mu
extend("aws")
extend("file")
extend("process")

credentials = aws:credentials([
  accessKeyId:     process:env("AWS_ACCESS_KEY_ID"),
  secretAccessKey: process:env("AWS_SECRET_ACCESS_KEY"),
  region:          process:env("AWS_REGION")
])

stream = aws:s3:fetch([
  credentials: credentials,
  bucket:      "my-bucket",
  key:         "large-object.bin"
])

file:write("/tmp/large-object.bin", stream)
```

Chunks are read in up to 8192‑byte blocks and delivered as *signed* ints (0–255) in an IntArray. The iterator finishes naturally when the stream ends.

> **More streaming examples:** examples/s3/fetch-stream.mu, examples/s3/fetch-stream-map.mu, examples/s3/fetch-multi.mu.

## API reference

### `aws:credentials(opts)` → *session_id:string*

| Key               | Type   | Required | Notes                                                      |
| :---------------- | :----- | :------- | :--------------------------------------------------------- |
| `accessKeyId`     | string | Yes      | AWS access key id                                          |
| `secretAccessKey` | string | Yes      | AWS secret                                                 |
| `region`          | string | Yes      | e.g., `us-east-1`                                          |
| `endpoint`        | string | No       | S3‑compatible endpoint (LocalStack, MinIO w/ S3 API, etc.) |

Validates inputs and stores an in‑memory session. Returns a short session id. On failure, the dynamic wrapper surfaces an error object:

```mu
[ok: false, error: "aws:credentials: ..."]
```

### `aws:s3:create_bucket(opts)` → *{ ok, bucket? , error? }*

| Key           | Type   | Required | Notes                             |
| :------------ | :----- | :------- | :-------------------------------- |
| `credentials` | string | Yes      | Session id from `aws:credentials` |
| `bucket`      | string | Yes      | Bucket name                       |

On success returns [ok: true, bucket: "…"]; on error returns [ok: false, error: "…"].

### `aws:s3:fetch(opts)` → *InkIterator* **or** *{ ok, path? , error? }*

| Key           | Type   | Required | Notes                                     |
| :------------ | :----- | :------- | :---------------------------------------- |
| `credentials` | string | Yes      | Session id                                |
| `bucket`      | string | Yes      | Bucket name                               |
| `key`         | string | Yes      | Object key                                |
| `path`        | string | No       | Full destination path (direct download)   |
| `folder`      | string | No       | Destination directory (filename inferred) |

- If `path` *or* `folder` is provided → downloads the object and returns [ok: true, path: "..."].
- If neither is provided → returns a streaming **InkIterator** of chunks `{ chunk: IntArray }`.

## How it works

- **Session store:** SESSIONS (lazy static HashMap<String, AwsSession>) keyed by session id. (src/register/credentials.rs)
- **SigV4:** canonical request + hex SHA‑256 + HMAC chain; returns ready‑to‑use headers (including Authorization). (src/functions/sigv4.rs)
- **Networking:** reqwest::blocking client. Direct download writes to a file; streaming spawns a reader thread that sends 8 KiB blocks over an MPSC channel to a MuMu plugin iterator. (src/register/s3_fetch.rs)
- **Error model:** all dynamic functions are guarded with catch_unwind; failures are returned as keyed arrays, never raw panics.

## Repository layout

```
LOCAL/
  src/
    functions/
      sigv4.rs
    register/
      credentials.rs
      s3_create_bucket.rs
      s3_fetch.rs
      hello.rs
  examples/
    s3/
      create-bucket.mu
      fetch.mu
      fetch-multi.mu
      fetch-stream.mu
      fetch-stream-map.mu
REMOTE/ (MuMu engine; vendored)
  src/
    parser/...   # lexer, parser, interpreter, eval helpers, etc.
    modules/...  # extend, compose/pipe, slog/sput, ink, include, type
```

The vendored MuMu engine includes improvements for λ by‑reference semantics, composition, numeric coercions, REPL input, autocompletion, and statement write‑through to reference cells.

## LocalStack tips

1. Run LocalStack with S3 enabled (e.g., via Docker).

2. Use test credentials and set

    

   ```
   endpoint
   ```

    

   in

    

   ```
   aws:credentials
   ```

   :

   ```mu
   credentials = aws:credentials([
     accessKeyId:     "test",
     secretAccessKey: "test",
     region:          "us-east-1",
     endpoint:        "http://localhost:4566"
   ])
   ```

3. Use the same APIs as you would for AWS.

## Troubleshooting

- **“no session with id …”** — The session id is missing or stale. Call aws:credentials again and reuse its return value.

- **“AWS error status …”** — Check bucket permissions, region, and the exact bucket/key. For S3‑compatible endpoints ensure the endpoint is set in aws:credentials.

- Plugin not found

   

  — Ensure

   

  MUMU_PLUGIN_PATH

   

  includes the directory with the compiled library. The loader also searches near the MuMu binary and common system locations:

  - Current exe dir and ../lib
  - CWD: ./libmumuaws.* or ./aws/libmumuaws.*
  - Linux: /usr/local/lib, /usr/lib64, /usr/lib
  - macOS: /usr/local/lib, /opt/homebrew/lib
  - Windows: %ProgramFiles%\lava-mumu\lib

## Security

- Sessions are in‑memory only; no persistence.
- Prefer process:env("…") for secrets; avoid hard‑coding.
- Use S3‑compatible stacks (e.g., LocalStack) for development and tests.

## License

MuMu engine files reference the MIT license in headers. If this plugin has a separate license, consult the repository’s LICENSE. Example files are provided for educational use.

## Acknowledgements

Thanks to the MuMu contributors for the runtime, REPL, and libraries. This plugin follows MuMu’s dynamic function conventions (extend("aws")) and uses **InkIterator** for streaming downloads.

------

© 2025 MuMu × AWS Plugin