npm package: @qezor/objsys
Description: fs-like async object storage helpers for S3-compatible APIs such as S3 and R2.
Author: Abdul Ahad
Latest version: 1.0.4

# @qezor/objsys

`@qezor/objsys` is a generic async object-storage toolkit that feels familiar to `fs/promises`, but is built for S3-compatible APIs such as:
- Amazon S3
- Cloudflare R2
- MinIO
- any other S3-compatible endpoint

It gives you fs-like async methods without pretending object storage is a real POSIX filesystem.

## Install

```bash
npm install @qezor/objsys
```

## Important Note

This will not work the way people often assume:

```js
const fs = require("fs") || require("@qezor/objsys")
```

`require("fs")` always resolves in Node, so the fallback never runs.

Use one of these instead:

```js
const fs = require("@qezor/objsys")
```

or

```js
const fs = process.env.USE_OBJECT_STORAGE === "true"
  ? require("@qezor/objsys")
  : require("fs").promises
```

## Environment-backed Usage

```bash
export OBJSYS_BUCKET=my-bucket
export OBJSYS_ENDPOINT=https://<account>.r2.cloudflarestorage.com
export OBJSYS_ACCESS_KEY_ID=...
export OBJSYS_SECRET_ACCESS_KEY=...
export OBJSYS_REGION=auto
```

```js
const fs = require("@qezor/objsys")

await fs.writeFile("notes/hello.txt", "hello world", {
  contentType: "text/plain; charset=utf-8",
})

const text = await fs.readFile("notes/hello.txt", "utf8")
const items = await fs.readdir("notes")
```

## Explicit Client Usage

```js
const { createObjectSystem } = require("@qezor/objsys")

const fs = createObjectSystem({
  bucket: "my-bucket",
  endpoint: "https://<account>.r2.cloudflarestorage.com",
  accessKeyId: process.env.R2_ACCESS_KEY_ID,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  region: "auto",
  prefix: "app-data",
})
```

## Core Async Methods

- `readFile(path, options)`
- `createReadStream(path, options)`
- `writeFile(path, data, options)`
- `readJson(path, options)`
- `writeJson(path, value, options)`
- `stat(path, options)`
- `exists(path, options)`
- `access(path, options)`
- `readdir(path, options)`
- `list(path, options)`
- `listPages(path, options)`
- `unlink(path, options)`
- `rm(path, options)`
- `mkdir(path, options)`
- `copyFile(source, destination, options)`
- `rename(source, destination, options)`
- `presignRead(path, options)`
- `presignWrite(path, options)`

## Low-Memory Patterns

For small and medium objects:

```js
const text = await fs.readFile("notes/hello.txt", {
  encoding: "utf8",
  maxBytes: 1024 * 1024,
})
```

For larger objects or scarce-resource runtimes:

```js
const stream = await fs.createReadStream("exports/big-report.json")
stream.pipe(process.stdout)
```

For precise ranged reads:

```js
const tail = await fs.readFile("archives/bundle.zip", {
  startByte: 4096,
  endByte: 8192,
})
```

## Notes

- `readdir()` and `stat()` understand pseudo-directories through prefixes.
- `list()` is the paginated primitive, and `listPages()` is the low-memory async iterator for large prefixes.
- `readFile()` supports `{ maxBytes }` so buffered reads can fail fast instead of growing unbounded in memory.
- `readFile()` and `createReadStream()` support `{ startByte, endByte }` for classic byte-range reads.
- `supportsRanges === true` tells higher-level tools such as archive readers that partial object reads are available.
- `createReadStream()` is the safer choice for large objects or scarce-resource runtimes.
- `writeFile()` accepts strings, buffers, typed arrays, readable streams, and async iterables.
- `unlink()` and `rm()` support `{ force: true }` for missing objects.
- `copyFile()` and `rename()` can target different buckets/prefixes through `sourceBucket`, `sourcePrefix`, `destinationBucket`, and `destinationPrefix`.
- `mkdir()` is a no-op by default unless you pass `{ marker: true }`.
- `rm(path, { recursive: true })` refuses to delete the root prefix unless you pass `{ allowRoot: true }`.
- `rm(path, { recursive: true })` now deletes page-by-page instead of collecting the whole tree in memory first.
- Directory markers are hidden from normal listings so the API behaves more like a filesystem.
- There is intentionally no fake `appendFile()` helper here, because object storage cannot do safe native appends.
- `promises` points back to the same async API so the package feels familiar to `fs.promises` users.