{"id":28710141,"url":"https://github.com/good-lly/s3mini","last_synced_at":"2025-09-15T09:47:22.183Z","repository":{"id":293025957,"uuid":"936532892","full_name":"good-lly/s3mini","owner":"good-lly","description":"👶 Tiny S3 client. Edge computing ready. No-dep. In Typescript. Works with @cloudflare @minio @backblaze @digitalocean @garagehq @oracle","archived":false,"fork":false,"pushed_at":"2025-09-08T09:43:46.000Z","size":1997,"stargazers_count":1243,"open_issues_count":3,"forks_count":17,"subscribers_count":3,"default_branch":"dev","last_synced_at":"2025-09-11T09:45:37.785Z","etag":null,"topics":["aws-s3","backblaze","ceph","ceph-radosgw","cephfs","cloudflare","digitalocean","edge-computing","garage","minio","oracle","s3","s3-client","s3-storage","serverless","typescript-library"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/s3mini","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/good-lly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["good-lly"]}},"created_at":"2025-02-21T08:50:07.000Z","updated_at":"2025-09-10T09:25:12.000Z","dependencies_parsed_at":"2025-05-13T10:39:12.036Z","dependency_job_id":"fc4dd9b3-d49a-416a-978f-15a58e17b9c9","html_url":"https://github.com/good-lly/s3mini","commit_stats":null,"previous_names":["good-lly/s3mini"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/good-lly/s3mini","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/good-lly%2Fs3mini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/good-lly%2Fs3mini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/good-lly%2Fs3mini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/good-lly%2Fs3mini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/good-lly","download_url":"https://codeload.github.com/good-lly/s3mini/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/good-lly%2Fs3mini/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275007400,"owners_count":25389639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-13T02:00:10.085Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-s3","backblaze","ceph","ceph-radosgw","cephfs","cloudflare","digitalocean","edge-computing","garage","minio","oracle","s3","s3-client","s3-storage","serverless","typescript-library"],"created_at":"2025-06-14T21:01:13.240Z","updated_at":"2025-09-15T09:47:22.148Z","avatar_url":"https://github.com/good-lly.png","language":"TypeScript","funding_links":["https://github.com/sponsors/good-lly"],"categories":["JavaScript","TypeScript"],"sub_categories":[],"readme":"# s3mini | Tiny \u0026 fast S3 client for node and edge platforms.\n\n`s3mini` is an ultra-lightweight Typescript client (~14 KB minified, ≈15 % more ops/s) for S3-compatible object storage. It runs on Node, Bun, Cloudflare Workers, and other edge platforms. It has been tested on Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Ceph, Oracle, Garage and MinIO. (No Browser support!)\n\n[[github](https://github.com/good-lly/s3mini)]\n[[issues](https://github.com/good-lly/s3mini/issues)]\n[[npm](https://www.npmjs.com/package/s3mini)]\n\n## Features\n\n- 🚀 Light and fast: averages ≈15 % more ops/s and only ~14 KB (minified, not gzipped).\n- 🔧 Zero dependencies; supports AWS SigV4 (no pre-signed requests).\n- 🟠 Works on Cloudflare Workers; ideal for edge computing, Node, and Bun (no browser support).\n- 🔑 Only the essential S3 APIs—improved list, put, get, delete, and a few more.\n- 📦 **BYOS3** — _Bring your own S3-compatible bucket_ (tested on Cloudflare R2, Backblaze B2, DigitalOcean Spaces, MinIO, Garage, Micro/Ceph and Oracle Object Storage).\n\n#### Tested On\n\n![Tested On](testedon.png)\n\nDev:\n\n![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/good-lly/s3mini/dev?color=greeen)\n![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/good-lly/s3mini)\n[![CodeQL Advanced](https://github.com/good-lly/s3mini/actions/workflows/codeql.yml/badge.svg?branch=dev)](https://github.com/good-lly/s3mini/actions/workflows/codeql.yml)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=good-lly_s3mini\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=good-lly_s3mini)\n[![Test:e2e(all)](https://github.com/good-lly/s3mini/actions/workflows/test-e2e.yml/badge.svg?branch=dev)](https://github.com/good-lly/s3mini/actions/workflows/test-e2e.yml)\n\n![GitHub Repo stars](https://img.shields.io/github/stars/good-lly/s3mini?style=social)\n![NPM Downloads](https://img.shields.io/npm/dm/s3mini)\n![NPM Version](https://img.shields.io/npm/v/s3mini?color=green)\n![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/s3mini?color=green)\n![GitHub License](https://img.shields.io/github/license/good-lly/s3mini)\n\n\u003ca href=\"https://github.com/good-lly/s3mini/issues/\"\u003e \u003cimg src=\"https://img.shields.io/badge/contributions-welcome-brightgreen.svg\" alt=\"Contributions welcome\" /\u003e\u003c/a\u003e\n\nPerformance tests was done on local Minio instance. Your results may vary depending on environment and network conditions, so take it with a grain of salt.\n![performance-image](https://raw.githubusercontent.com/good-lly/s3mini/dev/performance-screenshot.png)\n\n## Table of Contents\n\n- [Supported Ops](#supported-ops)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Security Notes](#security-notes)\n- [💙 Contributions welcomed!](#contributions-welcomed)\n- [License](#license)\n\n## Supported Ops\n\nThe library supports a subset of S3 operations, focusing on essential features, making it suitable for environments with limited resources.\n\n#### Bucket ops\n\n- ✅ HeadBucket (bucketExists)\n- ✅ createBucket (createBucket)\n\n#### Objects ops\n\n- ✅ ListObjectsV2 (listObjects)\n- ✅ GetObject (getObject, getObjectResponse, getObjectWithETag, getObjectRaw, getObjectArrayBuffer, getObjectJSON)\n- ✅ PutObject (putObject)\n- ✅ DeleteObject (deleteObject)\n- ✅ DeleteObjects (deleteObjects)\n- ✅ HeadObject (objectExists, getEtag, getContentLength)\n- ✅ listMultipartUploads\n- ✅ CreateMultipartUpload (getMultipartUploadId)\n- ✅ completeMultipartUpload\n- ✅ abortMultipartUpload\n- ✅ uploadPart\n- ✅ CopyObject: Local copyObject/moveObject(copyObject w delete)\n\nPut/Get objects with SSE-C (server-side encryption with customer-provided keys) is supported, but only tested on Cloudflare R2!\n\n## Installation\n\n```bash\nnpm install s3mini\n```\n\n```bash\nyarn add s3mini\n```\n\n```bash\npnpm add s3mini\n```\n\n### Environment Variables\n\nTo use `s3mini`, you need to set up your environment variables for provider credentials and S3 endpoint. Create a `.env` file in your project root directory. Checkout the [example.env](example.env) file for reference.\n\n```bash\n# On Windows, Mac, or Linux\nmv example.env .env\n```\n\n\u003e **⚠️ Environment Support Notice**\n\u003e\n\u003e This library is designed to run in environments like **Node.js**, **Bun**, and **Cloudflare Workers**. It does **not support browser environments** due to the use of Node.js APIs and polyfills.\n\u003e\n\u003e **Cloudflare Workers:** Now works without `nodejs_compat` compatibility flag, using native WebCrypto!\n\n## Usage\n\n\u003e [!WARNING]\n\u003e `s3mini` was a deprecated alias removed in a recent `0.5.0` release. Please migrate to the new `S3mini` class.\n\n```typescript\nimport { S3mini, sanitizeETag } from 's3mini';\n\nconst s3client = new S3mini({\n  accessKeyId: config.accessKeyId,\n  secretAccessKey: config.secretAccessKey,\n  endpoint: config.endpoint,\n  region: config.region,\n});\n\n// Basic bucket ops\nlet exists: boolean = false;\ntry {\n  // Check if the bucket exists\n  exists = await s3client.bucketExists();\n} catch (err) {\n  throw new Error(`Failed bucketExists() call, wrong credentials maybe: ${err.message}`);\n}\nif (!exists) {\n  // Create the bucket based on the endpoint bucket name\n  await s3client.createBucket();\n}\n\n// Basic object ops\n// key is the name of the object in the bucket\nconst smallObjectKey: string = 'small-object.txt';\n// content is the data you want to store in the object\n// it can be a string or Buffer (recommended for large objects)\nconst smallObjectContent: string = 'Hello, world!';\n\n// check if the object exists\nconst objectExists: boolean = await s3client.objectExists(smallObjectKey);\nlet etag: string | null = null;\nif (!objectExists) {\n  // put/upload the object, content can be a string or Buffer\n  // to add object into \"folder\", use \"folder/filename.txt\" as key\n  // Third argument is optional, it can be used to set content type ... default is 'application/octet-stream'\n  const resp: Response = await s3client.putObject(smallObjectKey, smallObjectContent);\n  // example with content type:\n  // const resp: Response = await s3client.putObject(smallObjectKey, smallObjectContent, 'image/png');\n  // you can also get etag via getEtag method\n  // const etag: string = await s3client.getEtag(smallObjectKey);\n  etag = sanitizeETag(resp.headers.get('etag'));\n}\n\n// get the object, null if not found\nconst objectData: string | null = await s3client.getObject(smallObjectKey);\nconsole.log('Object data:', objectData);\n\n// get the object with ETag, null if not found\nconst response2: Response = await S3mini.getObject(smallObjectKey, { 'if-none-match': etag });\nif (response2) {\n  // ETag changed so we can get the object data and new ETag\n  // Note: ETag is not guaranteed to be the same as the MD5 hash of the object\n  // ETag is sanitized to remove quotes\n  const etag2: string = sanitizeETag(response2.headers.get('etag'));\n  console.log('Object data with ETag:', response2.body, 'ETag:', etag2);\n} else {\n  console.log('Object not found or ETag does match.');\n}\n\n// list objects in the bucket, null if bucket is empty\n// Note: listObjects uses listObjectsV2 API and iterate over all pages\n// so it will return all objects in the bucket which can take a while\n// If you want to limit the number of objects returned, use the maxKeys option\n// If you want to list objects in a specific \"folder\", use \"folder/\" as prefix\n// Example s3client.listObjects({\"/\" \"myfolder/\"})\nconst list: object[] | null = await s3client.listObjects();\nif (list) {\n  console.log('List of objects:', list);\n} else {\n  console.log('No objects found in the bucket.');\n}\n\n// delete the object\nconst wasDeleted: boolean = await s3client.deleteObject(smallObjectKey);\n// to delete multiple objects, use deleteObjects method\n// const keysToDelete: string[] = ['object1.txt', 'object2.txt'];\n// const deletedArray: boolean[] = await s3client.deleteObjects(keysToDelete);\n// Note: deleteObjects returns an array of booleans, one for each key, indicating if the object was deleted or not\n\n// Multipart upload\nconst multipartKey = 'multipart-object.txt';\nconst large_buffer = new Uint8Array(1024 * 1024 * 15); // 15 MB buffer\nconst partSize = 8 * 1024 * 1024; // 8 MB\nconst totalParts = Math.ceil(large_buffer.length / partSize);\n// Beware! This will return always a new uploadId\n// if you want to use the same uploadId, you need to store it somewhere\nconst uploadId = await s3client.getMultipartUploadId(multipartKey);\nconst uploadPromises = [];\nfor (let i = 0; i \u003c totalParts; i++) {\n  const partBuffer = large_buffer.subarray(i * partSize, (i + 1) * partSize);\n  // upload each part\n  // Note: uploadPart returns a promise, so you can use Promise.all to upload all parts in parallel\n  // but be careful with the number of parallel uploads, it can cause throttling\n  // or errors if you upload too many parts at once\n  // You can also use generator functions to upload parts in batches\n  uploadPromises.push(s3client.uploadPart(multipartKey, uploadId, partBuffer, i + 1));\n}\nconst uploadResponses = await Promise.all(uploadPromises);\nconst parts = uploadResponses.map((response, index) =\u003e ({\n  partNumber: index + 1,\n  etag: response.etag,\n}));\n// Complete the multipart upload\nconst completeResponse = await s3client.completeMultipartUpload(multipartKey, uploadId, parts);\nconst completeEtag = completeResponse.etag;\n\n// List multipart uploads\n// returns object with uploadId and key\nconst multipartUploads: object = await s3client.listMultipartUploads();\n// Abort the multipart upload\nconst abortResponse = await s3client.abortMultipartUpload(multipartUploads.key, multipartUploads.uploadId);\n\n// Multipart download\n// lets test getObjectRaw with range\nconst rangeStart = 2048 * 1024; // 2 MB\nconst rangeEnd = 8 * 1024 * 1024 * 2; // 16 MB\nconst rangeResponse = await s3client.getObjectRaw(multipartKey, false, rangeStart, rangeEnd);\nconst rangeData = await rangeResponse.arrayBuffer();\n\n// Local copyObject example\nconst result = await s3.copyObject('report-2024.pdf', 'archive/report-2024.pdf');\n```\n\nFor more check [USAGE.md](USAGE.md) file, examples and tests.\n\n## Security Notes\n\n- The library masks sensitive information (access keys, session tokens, etc.) when logging.\n- Always protect your AWS credentials and avoid hard-coding them in your application (!!!). Use environment variables. Use environment variables or a secure vault for storing credentials.\n- Ensure you have the necessary permissions to access the S3 bucket and perform operations.\n- Be cautious when using multipart uploads, as they can incur additional costs if not managed properly.\n- Authors are not responsible for any data loss or security breaches resulting from improper usage of the library.\n- If you find a security vulnerability, please report it to us directly via email. For more details, please refer to the [SECURITY.md](SECURITY.md) file.\n\n## Contributions welcomed! (in specific order)\n\nContributions are greatly appreciated! If you have an idea for a new feature or have found a bug, we encourage you to get involved in this order:\n\n1. _Open/Report Issues or Ideas_: If you encounter a problem, have an idea or a feature request, please open an issue on GitHub (FIRST!) . Be concise but include as much detail as necessary (environment, error messages, logs, steps to reproduce, etc.) so we can understand and address the issue and have a dialog.\n\n2. _Create Pull Requests_: We welcome PRs! If you want to implement a new feature or fix a bug, feel free to submit a pull request to the latest `dev branch`. For major changes, it's a necessary to discuss your plans in an issue first!\n\n3. _Lightweight Philosophy_: When contributing, keep in mind that s3mini aims to remain lightweight and dependency-free. Please avoid adding heavy dependencies. New features should provide significant value to justify any increase in size.\n\n4. _Community Conduct_: Be respectful and constructive in communications. We want a welcoming environment for all contributors. For more details, please refer to our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). No one reads it, but it's there for a reason.\n\nIf you figure out a solution to your question or problem on your own, please consider posting the answer or closing the issue with an explanation. It could help the next person who runs into the same thing!\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.\n\n## Sponsor This Project\n\nDeveloping and maintaining s3mini (and other open-source projects) requires time and effort. If you find this library useful, please consider sponsoring its development. Your support helps ensure I can continue improving s3mini and other projects. Thank you!\n\n[![Become a Sponsor](https://img.shields.io/badge/💸_GitHub-Sponsor-ff69b4?logo=github\u0026logoColor=white)](https://github.com/sponsors/good-lly)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgood-lly%2Fs3mini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgood-lly%2Fs3mini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgood-lly%2Fs3mini/lists"}