@labelbox/horizon-sdk
v0.0.1
Published
TypeScript SDK for Horizon
Maintainers
Keywords
Readme
@labelbox/horizon-sdk
The internal Horizon TypeScript SDK workspace, generated from the backend OpenAPI spec with
@hey-api/openapi-ts. Almost the entire package is
generated. A small hand-written layer supplies client construction and options,
path nesting, the session SSE policy the generated reconnecting transport does
not preserve, and the reference and recipe schemas.
The Horizon npm package is not bootstrapped yet. Its canonical API is already Horizon-native so candidate archives exercise the exact surface that will be published:
import { createHorizonClient } from '@labelbox/horizon-sdk';
const apiKey = process.env.LABELBOX_API_KEY;
if (!apiKey) throw new Error('LABELBOX_API_KEY is required');
const horizon = createHorizonClient({ apiKey });
const { data: environment, response } = await horizon.environments.get({
environmentId: '784e2386-e297-4f9d-a886-838422383b65',
});
console.log(response.status, environment.name);Publication
@labelbox/horizon-sdk is not published. The manual Release / TypeScript SDK
workflow builds and externally consumes an immutable 0.0.1 bootstrap archive,
but the executable release authority refuses npm and GitHub mutation until a
Labelbox npm owner reserves the package and registers its Trusted Publisher.
@labelbox/recursion-sdk belongs to enterprise Recursion; Horizon owns the
distinct horizon-sdk-v* preview line after activation.
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: horizon.<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')
→ horizon.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 horizon.synthesizers (jobs) and horizon.synthesizerRuns (runs).
Regenerating
# Regenerate the client + the SDK reference docs from the committed spec snapshot.
yarn generate sdk
# Also re-dump the spec snapshot (packages/horizon-sdk/openapi.json) from the backend first.
yarn generate sdk --refresh-specsrc/generated/**, src/generated-stream/**, 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 intosrc/generatedso the SDK is self-contained and version-locked to the generator (the standalone@hey-api/client-fetchpackage drifts independently).@hey-api/client-fetchis therefore a devDependency, used only at generation time.throwOnError: true+responseStyle: 'fields'— generated methods return{ data, request, response }and throw non-2xx response bodies.response.statusandresponse.headerspreserve protocol metadata such asETag,Location, andIdempotency-Replayed; binary response data is aBlob. A conditional304is not a Fetch success (Response.okis false), so callers expecting it pass{ throwOnError: false }for that request and inspect the returned nativeresponse.statusandresponse.headers. Headers, abort signals, and the fetch implementation remain overrideable per request. For a bodyless204, treatresponse.statusas authoritative and do not inspectdata: its runtime sentinel is an internal Hey API detail while the OpenAPI-derived TypeScript data type isvoid.output.importFileExtension: '.js'— makes generated relative imports compatible with the repo'smoduleResolution: 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/generatedexcluded 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.
