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

# Async and concurrency

> Process documents concurrently with the TypeScript SDK

The TypeScript SDK has one asynchronous `NdiClient`. Every network method
returns a Promise, so the same client works for sequential calls, concurrent
batches, and server applications.

## Basic usage

```ts theme={"dark"}
import { NdiClient, isParseResult } from "ndi-sdk";

const client = new NdiClient();
const upload = await client.documents.createUpload("report.pdf");
const queued = await client.documents.parse(upload);
const job = await client.jobs.wait(queued.job_id, { timeout: 600 });

if (isParseResult(job.result)) {
  console.log(job.result.markdown);
}
```

Unlike the Python SDK, there is no separate async client and no synchronous
client. Use `await` for one operation and Promise utilities for concurrency.

## Process documents concurrently

Share one client across tasks. Each operation creates its own job, and each
`jobs.wait` polls independently.

```ts theme={"dark"}
import { NdiClient, isParseResult } from "ndi-sdk";

const client = new NdiClient();
const files = ["invoice.pdf", "contract.pdf", "report.pdf"];

const results = await Promise.all(
  files.map(async (path) => {
    const upload = await client.documents.createUpload(path);
    const queued = await client.documents.parse(upload);
    const job = await client.jobs.wait(queued.job_id, { timeout: 600 });
    return isParseResult(job.result) ? job.result.markdown : null;
  }),
);

console.log(results.map((result) => result?.length ?? 0));
```

## Limit concurrency

For large batches, use a small worker pool so your application does not submit
every file at once.

```ts theme={"dark"}
import { NdiClient, isParseResult } from "ndi-sdk";

async function parseBatch(paths: string[], maxConcurrent = 5) {
  const client = new NdiClient();
  const results: Array<string | null> = new Array(paths.length).fill(null);
  let nextIndex = 0;

  async function worker() {
    while (nextIndex < paths.length) {
      const index = nextIndex++;
      const upload = await client.documents.createUpload(paths[index]);
      const queued = await client.documents.parse(upload);
      const job = await client.jobs.wait(queued.job_id, { timeout: 600 });
      results[index] = isParseResult(job.result) ? job.result.markdown : null;
    }
  }

  await Promise.all(
    Array.from(
      { length: Math.min(maxConcurrent, paths.length) },
      () => worker(),
    ),
  );
  return results;
}
```

Each worker holds one complete upload, submission, and wait cycle at a time.
This bounds the number of active jobs without adding another package.

## Handle partial batch failures

`Promise.all` rejects when one task rejects. Use `Promise.allSettled` when each
document should succeed or fail independently.

```ts theme={"dark"}
const files = ["invoice.pdf", "contract.pdf", "report.pdf"];

const results = await Promise.allSettled(
  files.map(async (path) => {
    const upload = await client.documents.createUpload(path);
    const queued = await client.documents.parse(upload);
    return client.jobs.wait(queued.job_id, { timeout: 600 });
  }),
);

for (const [index, result] of results.entries()) {
  if (result.status === "rejected") {
    console.error(`${files[index]}:`, result.reason);
  } else {
    console.log(`${files[index]}: ${result.value.status}`);
  }
}
```

See [Error handling](/sdks/typescript/error-handling) for typed HTTP and job
errors.

## Client lifecycle

`close()` is currently a no-op because `fetch` has no client resource to
close. One `NdiClient` can be shared for the lifetime of an application.

```ts theme={"dark"}
const client = new NdiClient();
const jobId = "550e8400-e29b-41d4-a716-446655440003";
try {
  const job = await client.jobs.get(jobId);
  console.log(job.status);
} finally {
  client.close();
}
```

## When to use concurrency

<CardGroup cols={2}>
  <Card title="Run concurrently" icon="check">
    Independent documents, server request handlers, and batches where waiting
    on one job should not block submission of another.
  </Card>

  <Card title="Run sequentially" icon="arrow-right">
    One-off scripts or dependent operations where the next request needs the
    previous result.
  </Card>
</CardGroup>

## Next steps

* [Job management](/sdks/typescript/job-management)
* [Error handling](/sdks/typescript/error-handling)
* [Parse documents](/sdks/typescript/parse)
