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

# Shutdown

> Drain the queue correctly on every runtime

Two methods drain the queue:

```typescript theme={null}
logger.flush(timeoutMs?)     // drain in-flight; logger stays active
logger.shutdown(timeoutMs?)  // drain + stop timer + disable logger (idempotent)
```

Both return a `Promise<void>`. Both honor the timeout — if events are still buffered when the deadline hits, they're dropped and `onError` fires. Without a timeout, they wait as long as it takes (unbounded).

## What the SDK wires up for you

By default (`flushOnExit: true`), `createBoundaryLogger` attaches listeners to runtime lifecycle hooks that are **safe** to attach silently:

| Runtime        | Hook                                              | Behavior                                                                                                                        |
| -------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Node           | `process.beforeExit`                              | Fires once the event loop is otherwise empty. Node waits for awaited work inside the handler, so this is the ideal drain point. |
| Browser        | `document.visibilitychange` (hidden) + `pagehide` | Best-effort — if the browser is killing the tab, the flush may not finish.                                                      |
| Edge / Workers | *none*                                            | Runtime freezes between invocations; there is no reliable hook. You must `await logger.flush()` per request.                    |

## What the SDK does **not** do

The SDK deliberately does **not** attach handlers for `SIGTERM` or `SIGINT`. Those signals belong to your application's lifecycle — web servers, database clients, and queue consumers install their own handlers, and a silent SDK listener would either race with yours or keep the process alive past what Ctrl+C should do.

For signal coverage, install your own handler and call `shutdown()`:

```typescript theme={null}
process.once("SIGTERM", async () => {
  await server.close();
  await logger.shutdown(2000);  // flush with a 2s cap
  process.exit(0);
});

process.once("SIGINT", async () => {
  await logger.shutdown(2000);
  process.exit(0);
});
```

## `flush()` vs `shutdown()`

|                          | `flush()`                          | `shutdown()`         |
| ------------------------ | ---------------------------------- | -------------------- |
| Drains the queue         | yes                                | yes                  |
| Stops the periodic timer | no                                 | yes                  |
| Disables further sends   | no                                 | yes (idempotent)     |
| Use when                 | you want to checkpoint mid-process | you're about to exit |

Safe to call `shutdown()` multiple times — every call after the first resolves immediately.

## Timeout semantics

```typescript theme={null}
await logger.shutdown(2000);
```

* Waits up to 2000ms for in-flight writes to finish.
* Events still queued after the deadline are dropped; `onError` fires with a `ShutdownTimeoutError`-like diagnostic.
* Without a timeout, `shutdown()` waits indefinitely — fine for short-lived scripts, **not** for signal handlers (you'll block Ctrl+C).

Rule of thumb: pass a timeout in every signal handler and every serverless request.

## Opting out of the exit hooks

If you own all shutdown pathways yourself and don't want the SDK attaching anything:

```typescript theme={null}
createBoundaryLogger({
  flushOnExit: false,
});
```

Now nothing gets drained unless you call `flush()` or `shutdown()` explicitly. Good for tightly-controlled runtimes (custom process supervisors, test harnesses).

## Recipes by runtime

### Long-running Node server

```typescript theme={null}
const logger = createBoundaryLogger({ apiKey });

for (const signal of ["SIGTERM", "SIGINT"] as const) {
  process.once(signal, async () => {
    await server.close();
    await logger.shutdown(2000);
    process.exit(0);
  });
}
```

### AWS Lambda / Vercel Function

```typescript theme={null}
export async function handler(event) {
  try {
    return await handleRequest(event);
  } finally {
    await logger.flush(1000);
  }
}
```

### Cloudflare Workers

```typescript theme={null}
export default {
  async fetch(request, env, ctx) {
    const res = await handleRequest(request);
    ctx.waitUntil(logger.flush(1000));
    return res;
  },
};
```

`ctx.waitUntil` lets the runtime keep the isolate alive until the flush resolves, without blocking the response.

### Next.js App Router route handler

```typescript theme={null}
export async function POST(request: Request) {
  try {
    const body = await request.json();
    return Response.json(await handle(body));
  } finally {
    await logger.flush(1000);
  }
}
```

## See also

<CardGroup cols={2}>
  <Card title="Node" icon="node" href="/sdk/platforms/node">
    Long-running servers, beforeExit
  </Card>

  <Card title="Cloudflare / Workers / Edge" icon="cloud" href="/sdk/platforms/cloudflare-workers-edge">
    ctx.waitUntil pattern
  </Card>

  <Card title="Lambda / Vercel Functions" icon="aws" href="/sdk/platforms/vercel-aws-lambda">
    Per-invocation flush
  </Card>

  <Card title="Browser" icon="browser" href="/sdk/platforms/browser">
    pagehide + visibilitychange
  </Card>
</CardGroup>
