> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revoengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK storage and batching

> Batch runtime calls safely and move small or large objects through the correct Storage path.

The SDK batches remote calls for throughput, but batching is not a transaction. Use Storage upload sessions when bytes are too large for the JSON runtime batch.

## Automatic batching

Calls queued in the same microtask are grouped. `Promise.all` is the normal way to create a batch:

```ts theme={null}
const [customers, agents, secret] = await Promise.all([
  revo.api.getDatabaseData('Customers', { take: 100 }),
  revo.agents.list(),
  revo.api.getSecret('CRM_API_TOKEN'),
]);
```

Sequential awaits naturally dispatch separate batches:

```ts theme={null}
const customers = await revo.api.getDatabaseData('Customers', { take: 100 });
const agents = await revo.agents.list();
```

Configure batching before the first remote dispatch:

```ts theme={null}
revo.batch.configure({
  failureMode: 'stop',
  maxCalls: 16,
  maxInFlight: 2,
});
```

| Failure mode  | Behavior                                                                        |
| ------------- | ------------------------------------------------------------------------------- |
| `independent` | Every valid call runs and its promise settles independently                     |
| `stop`        | Calls run in order; entries after the first rejection fail with `BATCH_STOPPED` |

The SDK defaults to 32 calls, 256 KiB per request, four in-flight batches, and 1,024 queued calls. Client configuration can only lower platform ceilings. The current server envelope allows up to 64 calls, a 1 MiB request, and a 4 MiB response.

<Warning>
  Runtime batches are never retried automatically because they can contain mutations. A transport interruption after dispatch produces an indeterminate result; inspect durable state before retrying.
</Warning>

## Small objects

Use `putObject()` when text or base64 data comfortably fits the runtime batch:

```ts theme={null}
const object = await revo.storage.putObject({
  name: 'settings.json',
  data: JSON.stringify({ locale: 'pl-PL', enabled: true }),
  dataEncoding: 'utf8',
  mimeType: 'application/json',
});

console.log(object.storageEntryId);
```

Binary data must be base64-encoded when sent through this JSON path.

## Large uploads with explicit parts

For data generated by the application, an `incremental` session gives each appended part the next number. Upload calls carry small text or base64 chunks through the SDK, while the durable entry appears only at finalization.

```ts theme={null}
import type { RevoRuntime } from '@revoengine/sdk';

export async function uploadGeneratedCsv(
  runtime: Pick<RevoRuntime, 'storage'>,
  name: string,
  rows: AsyncIterable<string>,
) {
  const { session } = await runtime.storage.createUploadSession({
    name,
    contentTypeHint: 'text/csv',
    uploadMode: 'incremental',
    computeStats: 'sync',
    metadata: { kind: 'customer-export' },
    retention: { ttlSeconds: 30 * 24 * 60 * 60 },
  });

  const sessionId = session.storageUploadSessionId;

  try {
    await runtime.storage.uploadPart(sessionId, {
      data: 'customerId,balance\n',
      dataEncoding: 'utf8',
    });

    for await (const row of rows) {
      await runtime.storage.uploadPart(sessionId, {
        data: `${row}\n`,
        dataEncoding: 'utf8',
      });
    }

    const state = await runtime.storage.getUploadSession(sessionId);
    console.log({ uploadedParts: state.uploadedParts, uploadedBytes: state.uploadedBytes });

    const { entry } = await runtime.storage.finalizeUploadSession(sessionId, {
      computeStats: 'sync',
    });
    return entry;
  } catch (error) {
    await runtime.storage.abortUploadSession(sessionId).catch(() => undefined);
    throw error;
  }
}
```

For `chunked`, pass an explicit positive `partNumber`; re-uploading that number repairs a part. For `incremental`, omit the number during normal appends but you may still provide one to repair a known part. `getUploadSession()` exposes the manifest, uploaded bytes, part count, and next number. An active session has a lease: call `extendUploadSession()` while a long-running producer is still making progress.

<Warning>
  Finalization is the durable boundary. A part acknowledged by `uploadPart()` is not yet a readable Storage entry and must not trigger downstream processing.
</Warning>

## Direct browser or Node upload

Choose `uploadMode: 'direct'` when bytes should travel through the managed upload URL rather than through SDK JSON calls:

```text theme={null}
createUploadSession({ uploadMode: 'direct', ... })
  -> send bytes to upload.uploadUrl using the exact method and headers
  -> poll getUploadSession(storageUploadSessionId)
  -> continue only when status is FINALIZED and targetStorageEntryId is present
```

The platform's managed completion flow finalizes a successful direct upload. Do not call finalization merely because `fetch()` returned success, and do not infer durable completion from the byte-transfer response alone. Poll the session or react to the resulting Storage event. When an instance returns another upload strategy, follow that strategy and its public completion contract.

Upload URLs and headers are temporary credentials. Do not log, persist, or pass them to another principal. If byte transfer fails before completion, abort the active session as a best-effort cleanup.

## Read large text in batches

Text statistics record detected encoding, separator, total line count, batch size, batch count, and the byte-offset map used for bounded reads.

```ts theme={null}
const stats = await revo.storage.getFileStats(storageEntryId, {
  separator: '\n',
  batchSize: 25_000,
});

for (let batchNumber = 1; batchNumber <= (stats.batches ?? 0); batchNumber += 1) {
  const lines = await revo.storage.getFileData(storageEntryId, { batchNumber });
  await processLines(lines);
}
```

`batchNumber` is one-based. A batch is a bounded line window backed by cached statistics; it is unrelated to SDK request batching. If statistics are missing, `getFileData()` can build them before reading. Use `reloadStats: true` only when the cached mapping must be recomputed.

For binary data, `getFileData()` returns base64. Direct reads are limited to 10 MiB; use `buffer: true` with an explicit byte `start` and `end` for a bounded range.

## Large data exports

When exporting database rows:

1. request bounded pages rather than one unbounded result;
2. use a stable unique sort key;
3. advance pagination only when the previous page made progress;
4. use a snapshot/version filter if the source can change during export;
5. generate the file locally in Node.js, then upload it through a session.

For an HTTP source that already exposes a stream, a RevoEngine Component can instead use `api.httpCall({ ... }, { responseType: 'stream', target: { ... } })` so the response is finalized directly into Storage.

Callback-based transaction compatibility APIs are intentionally not part of the public SDK. If several remote operations require atomicity, implement that boundary in one RevoEngine component or library where the database transaction context exists.

See [Storage operations](/operate/storage), [HTTP and Storage](/low-code/http-and-storage), and the generated [Storage method reference](/low-code/reference/storage).
