# @labelbox/recursion-sdk

The Recursion TypeScript SDK, **generated from the backend OpenAPI spec** with
[`@hey-api/openapi-ts`](https://heyapi.dev). Almost the entire package is
generated — the only hand-written code is the path reader (`src/nesting.ts`) and
a thin auth constructor (`src/index.ts`).

```ts
import { createRecursionClient } from '@labelbox/recursion-sdk';

const rl = createRecursionClient({ apiKey: process.env.LABELBOX_API_KEY });
const job = await rl.synthesizers.create({
  environmentId: 'env_…',
  body: { name: 'Variant generator', systemPrompt: '…' },
});
```

## Install

Published to **public npm** as `@labelbox/recursion-sdk` — no registry configuration,
repository access, or install-time credential is needed:

```sh
npm install @labelbox/recursion-sdk
```

Full setup steps:
`apps/horizon/docs/public/docs/ts-sdk-getting-started.md`. The SDK releases independently
of the CLI: a `packages/sdk-ts/**` change (e.g. a committed `openapi.json` update)
cuts an SDK release via the **Release / SDK** GitHub Action — see `yarn commands release`.

## Surface

Calls use flat params — path/query values and the request body are all top-level
keys of a single options argument; the request body sits under `body`.
Instance-based: `rl.<namespace>.<method>(...)`.

The body param would otherwise be named after its DTO (e.g.
`createSynthesizerJobBodyDto`) — `yarn generate sdk` renames it to `body` via a
deterministic post-pass (`normalizeGeneratedClient` in
`tools/dx/src/commands/sdk.ts`), since hey-api offers no lever for this. The same
guarded pass narrows hey-api's generic whole-slot map type to `body` after proving
that the generated SDK emits no header, path, or query slot mappings.

**Opt-in, declared at the source.** An operation is in the SDK iff its backend
handler carries `@SdkRoute(...segments)` (`apps/horizon/api/src/common/sdk-route.decorator.ts`),
which emits an `x-sdk-path` OpenAPI extension. `@SdkRoute('synthesizers', 'create')`
→ `rl.synthesizers.create(...)`. There is **no heuristic and no per-operation
code here** — `src/nesting.ts` just reads the declared paths and asserts (at
codegen time) that every path is unique and a valid identifier. Endpoints without
`@SdkRoute` are not generated (that is also how you exclude one).

Scope grows by annotating more handlers — e.g. the `synthesizer` tag is exposed
as `rl.synthesizers` (jobs) and `rl.synthesizerRuns` (runs).

## Regenerating

```sh
# Regenerate the client + the SDK reference docs from the committed spec snapshot.
yarn generate sdk

# Also re-dump the spec snapshot (packages/sdk-ts/openapi.json) from the backend first.
yarn generate sdk --refresh-spec
```

`src/generated/**` and `src/reference/*.generated.ts` are **git-ignored**. Regenerate
them from the committed `openapi.json` with `yarn generate prerequisites`. Cold installs
also generate them through root `postinstall` → `yarn build libs`; warm-cache builds and
checks that consume them explicitly own generation in their task or workflow. The shared
`setup-node-yarn` action only installs dependencies. Never hand-edit generated output;
only `openapi.json` is committed.

## Generation choices (and why)

- **`bundle: true`** — the fetch runtime is vendored into `src/generated` so the
  SDK is self-contained and version-locked to the generator (the standalone
  `@hey-api/client-fetch` package drifts independently). `@hey-api/client-fetch`
  is therefore a devDependency, used only at generation time.
- **`output.importFileExtension: '.js'`** — makes generated relative imports
  compatible with the repo's `moduleResolution: NodeNext`.
- **No `exactOptionalPropertyTypes: true`** (tsconfig) — the vendored fetch
  runtime does not satisfy this flag, so this package leaves it off (defaulting
  to false) while every other package in the repo opts in explicitly. It is the
  one strictness relaxation this generated-heavy package makes; hand-written code
  stays clean.
- **`src/generated` excluded from Biome** — generated output is not linted; its
  determinism is what the freshness check relies on.

## Docs

The generated SDK reference shown in the in-app API docs is derived from the
same spec by `yarn generate sdk` into this package's `./reference` export
(`SDK_REFERENCE`, with its `./schema` shape), keyed by `operationId` so each
operation's doc page renders its TS SDK / CLI / cURL examples.
