TypeScript usage
The SDK is types-first. Everything a call accepts and returns is generated from the API’s OpenAPI document and ships prebuilt in the package — you never run a generator, and you should never hand-edit the generated types. This page covers importing those types, letting inference do the work, and two known drifts to plan around.
What ships
// Runtime values
import {
createStorefrontClient,
createFlightdeckClient,
FlightdeckError,
} from "@dscodotco/sdk";
// Types
import type {
// Client-facing
StorefrontClient,
StorefrontClientOptions,
FlightdeckClient,
FlightdeckClientOptions,
// The low-level type toolkit
HttpMethod,
MethodsOf,
Operation,
RequestArgs,
ResponseBody,
// The generated OpenAPI surface
paths,
operations,
components,
} from "@dscodotco/sdk";paths— every API path mapped to its operations (method to operation object). This is whatrequest()is keyed on.operations— operations keyed by their operation id.components— the reusable schemas:components["schemas"]["StorefrontProduct"],["StorefrontOrder"],["StorefrontCheckoutRequest"],["ErrorBody"], and so on.
Let inference do the work
In the common path you never write a type annotation. The client methods already return their precise generated bodies:
const store = createStorefrontClient({ apiUrl, tenant, storefrontToken });
const { products } = await store.products.list();
// ^? StorefrontProduct[]
const result = await store.checkout.submit(body, { idempotencyKey });
// ^? the placed/replayed union — narrow on result.outcome
if (result.outcome === "placed") {
result.card_charged_cents; // number
}A wrong query field, body field, or path is a compile error. You do not need to restate the types the compiler already knows.
Reaching a request shape by name
To build a checkout body in a helper (away from the call site), pull the schema
from components:
import type { components } from "@dscodotco/sdk";
type CheckoutBody = components["schemas"]["StorefrontCheckoutRequest"];
function buildCheckoutBody(cart: Cart): CheckoutBody {
return {
customer_ref: `guest:${cart.email}`,
items: cart.lines.map((l) => ({ variant_id: l.variantId, quantity: l.qty })),
};
}Useful schema names: StorefrontProduct, StorefrontVariant,
StorefrontCollection, StorefrontOrder, StorefrontOrderItem,
StorefrontCheckoutRequest, StorefrontCard, StorefrontRefundRequest,
StorefrontCancelRequest, StorefrontEditRequest, SiteManifest, ErrorBody.
Reading a response type
To name a response type (for a component prop, say), derive it from paths with
the Operation and ResponseBody helpers:
import type { Operation, ResponseBody } from "@dscodotco/sdk";
type SiteResponse = ResponseBody<Operation<"/v1/tenants/{tenant}/site", "get">>;
// { tenant_ref: string; version: number; manifest: SiteManifest }
type ProductResponse = ResponseBody<Operation<"/v1/tenants/{tenant}/catalog/products/{slug}", "get">>;
// { product: StorefrontProduct }Operation<P, M>— the operation object for a(path, method)pair.ResponseBody<O>— that operation’s 2xx JSON body.RequestArgs<O>— the{ path?, query?, header?, body? }the operation accepts.MethodsOf<P>— the methods a path defines.
These are the exact helpers the storefront wrapper is built from, so what you derive matches what the methods return.
Typing the error body
FlightdeckError.body is unknown. Narrow it against the generated ErrorBody
rather than casting blindly:
import { FlightdeckError } from "@dscodotco/sdk";
import type { components } from "@dscodotco/sdk";
type ErrorBody = components["schemas"]["ErrorBody"];
function isErrorBody(v: unknown): v is ErrorBody {
return typeof v === "object" && v !== null && "error" in v;
}
catch (err) {
if (err instanceof FlightdeckError && isErrorBody(err.body)) {
err.body.error.code; // string
}
}Known type drift
Two places where the generated types do not line up with runtime behavior today. Plan around them; do not patch the generated files.
The price-filter drift
store.search (and the underlying /v1/tenants/{tenant}/catalog/search query)
declares its price filters as price_min_cents / price_max_cents. The
backend handler, however, reads min_price_cents / max_price_cents.
Passing the typed fields compiles cleanly but the filter never reaches the
backend — the results come back unfiltered by price.
Until the spec and handler agree, apply price filters through the low-level client with the field names the handler actually expects. Because those names are not in the generated query type, this needs a localized cast (a documented escape hatch, not a pattern to spread):
import { createFlightdeckClient } from "@dscodotco/sdk";
const sf = createFlightdeckClient({ baseUrl, storefrontToken });
const results = await sf.request("get", "/v1/tenants/{tenant}/catalog/search", {
path: { tenant: "ruo-demo" },
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- handler expects min_/max_price_cents; spec drift
query: { q: "peptide", min_price_cents: 1000, max_price_cents: 5000 } as any,
});Prefer the typed store.search({ q, collection, limit, offset }) for
everything except price. Only drop to the raw query for the price bounds, and
keep the cast to that one call so the rest of your search code stays typed.
Optional-but-required query arguments
A few storefront methods mark their query argument optional in the SDK while the API requires a field inside it:
| Method | SDK signature | API actually requires |
|---|---|---|
store.shopper.orders(query?) | query optional | person_id |
store.storeCredit.balance(query?) | query optional | customer_ref |
Always pass the field ({ person_id }, { customer_ref }) — the call compiles
without it but returns 400. And note store.shopper.order(id) cannot pass the
person_id the endpoint requires at all; fetch a single shopper order through
the low-level client
until that method is fixed.
Never generate or hand-edit types
As an external consumer you do not run openapi-typescript or any codegen. The
types that match a given API surface ship inside that version of the package;
upgrading @dscodotco/sdk brings the matching types. Do not hand-edit
types.generated.* to add a route or paper over a drift — the drift will
silently re-appear on the next upgrade, and a faked route will not actually
exist on the server.
See also
- Storefront client — the methods whose types this page derives.
- Merchant / low-level client —
request()and thepathskey it is built on. - Error handling —
FlightdeckErrorand theErrorBodyenvelope.