tako gen client ts — the generated TypeScript client
Declare
Nothing to declare — every browser-callable tako.API endpoint is in;
the command reads the app:
tako gen client ts [--out <path>]
Default output is .tako/gen/client/ts/api.ts; --out puts it where
your frontend wants it. Output is deterministic — the same code
produces a byte-identical file — so commit it and let a test pin its
freshness (shop does; see below). In
examples/shop/app.go:
$ tako gen client ts --out web/src/api.ts
client web/src/api.ts — 7 endpoints, 7 types
excluded: billing.Charge POST /charge — private (IAM-only — browsers cannot mint OIDC)
Exclusion is never silent — the reasons land in the generated header:
// Code generated by tako gen client ts. DO NOT EDIT.
//
// Typed client for the api endpoints of shop: plain fetch, zero
// dependencies. The session cookie rides every call (same-origin);
// non-GET requests attach X-CSRF-Token from the page's meta tag.
//
// Excluded (not browser-callable):
// billing.Charge POST /charge — private (IAM-only — browsers cannot mint OIDC)
Three things get excluded: Route.Private ("browsers cannot mint
OIDC"), multipart uploads ("the signed-URL flow, not the JSON
client"), and form-bound endpoints ("browsers post forms to it
directly").
Use
One typed method per endpoint — path params are positional args, the
JSON body an interface carrying your own type name, query params one
trailing optional object. Non-2xx throws ApiError with the status
and the message from tako's {"error": ...} body. From
examples/shop/web/src/app.ts:
import { createClient, ApiError, Product, Order } from "./api";
const api = createClient();
const order = await api.placeOrder({ productId: p.id, qty: 1 });
} catch (e) {
if (e instanceof ApiError && e.status === 401) {
location.assign("/_tako/signin?next=/");
return;
}
createClient({baseUrl, headers}): baseUrl targets a deployed
service (default same-origin); headers ride every call — non-browser
callers pass the session cookie here.
Local
The recommended loop: change the api, regenerate, and let your
frontend toolchain typecheck the result — shop's SiteConfig.Build esbuild
step bundles app.ts against the committed client, so signature
drift is a frontend build error. The client is a zero-dependency ES
module: no tako needed in frontend builds — just a committed file.
Cloud
Nothing extra deploys. In the browser the session cookie rides every
call and non-GET requests attach X-CSRF-Token from the page's meta
tag; from outside a browser, pass baseUrl and the session cookie via
headers (mint one with the curl recipe in
validate/shop.md).
Test it
Shop's examples/shop/api_test.go pins the
committed client to the generator's output — drift cannot merge:
if !bytes.Equal(content, committed) {
t.Fatal("web/src/api.ts is stale — regenerate: tako gen client ts --out web/src/api.ts")
}
Errors you'll meet
Generation failures are analyzer-style diagnostics with file:line:
<type> cannot ride JSON — client types are strings, bools, numbers, time.Time, slices, string-keyed maps, and exported structs of thosemap key <K> cannot ride JSON — client types need map[string]Tclient method <name> already generated from <var> — endpoint vars must map to distinct method namesclient type <T> is also declared at <pos> — client type names must be unique across the appembedded field <F> is not supported in client types — flatten it<T> binds path/query fields and also appears in a response — split the request type from the response type
Not yet
- TypeScript only:
tako gen clientgenerates TypeScript today — run it astako gen client ts. int64rides asnumber— values past 2^53 lose precision.- Excluded endpoints get a reason, not an alternative client: private
endpoints are for service calls, uploads for the
signed-URL flow, forms for the browser.
time.Timeis a plainstring(RFC 3339), not a branded type.