S3 clone
Store and retrieve objects in an S3-compatible twin — buckets, multipart uploads, lifecycle expiry on clock advance, presigned URLs, versioning, and object lock — without touching AWS.
Clone ID: s3 · Binary: clones-s3 · SDK kind: s3 · Registry status: shadow (in-memory engine; not a full AWS emulator) · Dashboard: S3 explorer
Start here
| Question | Answer |
|---|---|
| Best for | Invoice PDFs, report exports, presigned URL flows, lifecycle expiry, and notification webhooks |
| Connect with | AWS SDK v3 with custom endpoint, route mode for s3.amazonaws.com, @molar/clones SDK, or MCP bucket tools |
| Known limits | In-memory per run; no cross-region replication |
| Seeds | Empty by default; seed buckets via JSON or createBucket() |
Quick start
import { s3 } from "@molar/clones";
const clone = await s3.start({ seed: "invoice-store", runId: "run-s3-01" });
await clone.createBucket("invoices");
await clone.putObject("invoices", "2026/inv-001.pdf", pdfBytes);
const body = await clone.getObject("invoices", "2026/inv-001.pdf");
const listing = await clone.list("invoices");
await clone.stop();
MCP tools
| Tool | Description |
|---|---|
molar_clone_spawn | Spawn S3 clone (kind: "s3", optional seed) |
s3_create_bucket | Create bucket (url, runId, bucket) |
s3_put | Put object (bucket, key, body) |
s3_list | ListObjectsV2 for a bucket |
REST API coverage
S3-compatible API at {endpoint}/t/{runId}/:
| Operation | Support |
|---|---|
| CreateBucket / DeleteBucket | Yes |
| PutObject / GetObject / DeleteObject | Yes |
| ListObjectsV2 / list object versions | Yes |
| Multipart upload (initiate, parts, complete, abort) | Yes |
Bulk delete (?delete) | Yes |
| Range reads | Yes |
| Bucket lifecycle rules | Yes — expiry on clock advance |
| Presigned URLs | Yes — deterministic generation and clock-based expiry |
| Versioning | Yes |
| Object lock (retention / legal hold) | Yes |
| SSE, bucket ACL/policy, object tags | Yes |
| Event notifications | Yes — webhook delivery log for PUT, delete, multipart complete |
Seeds and initial state
There is no bundled storage-v1 seed. Either create buckets in your test or seed via admin:
await clone.adminPost(`/_clone/seed?runId=${clone.runId}`, {
buckets: [{ name: "invoices" }],
});
Dashboard preset Invoice PDF lifecycle (stripe + s3 + email) is the fastest way to try a full flow.
Limits
| Limit | Detail |
|---|---|
| Destructive-call refusal | SDK blocks *.amazonaws.com when credentials look like production keys |
| Lifecycle | advanceClock past expiration days to delete objects |
| Chaos presets | slow_put, 503_restore, lifecycle_early_delete |
AWS SDK configuration
import { S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: clone.endpoint + "/t/" + clone.runId,
region: "us-east-1",
credentials: {
accessKeyId: clone.accessKeyId,
secretAccessKey: clone.secretAccessKey,
},
forcePathStyle: true,
});
Or set:
AWS_ENDPOINT_URL=https://127.0.0.1:<port>/t/<runId>
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
Lifecycle and virtual clock
await clone.putBucketLifecycle("temp-uploads", { expirationDays: 7 });
await clone.advanceClock("8d");
const objects = await clone.list("temp-uploads");
// objects should be empty
Cross-clone flows
| Flow | Description |
|---|---|
| Stripe invoice PDF | Stripe finalizes invoice → your app stores PDF in S3 |
| Report export | App writes CSV; test lists and downloads |
Use the Invoice PDF lifecycle preset in the dashboard library.