Add an SDK endpoint
Arg skip: record the endpoint as skipped instead of wrapping it.
Run everything from packages/sdk.
Every generated endpoint must either have a call site under src/ or be listed in
src/coverage/skipped.ts. Being in both also fails.
0. Find it
Not in generated/ yet? bun run sync-specs && bun run generate (or ask the user to
run just update-generated if the Rust spec changed locally โ it rebuilds apps/web).
just coverage lists UNCOVERED <service>.<endpoint>.
1. Skip or wrap?
With the skip arg, skip. Otherwise ask the user, one line per endpoint, with a
short recommendation โ don't decide silently.
Skip-worthy: internal plumbing (auth/session, health, infra, web-app internals, MCP, batch previews) and features so narrow that no SDK user would reach for them. Everything a user could plausibly want belongs in the SDK.
To skip: add the method name, alphabetically, to <camelService>Excluded in
src/coverage/skipped.ts, then just coverage && just check. Don't wrap.
To wrap it
- Read the method in
generated/<service>/sdk.gen.tsand its types intypes.gen.ts(notepath/query/body). - Pick a home, and read a sibling first โ
entities/tasks/is the smallest complete example,entities/documents/document.tsthe richest:- acts on one entity โ method on that class
- create/list/search/lookup โ the namespace
- new noun โ new
src/entities/<noun>/pair, registered insrc/macro.ts - cross-entity capability โ a base in
src/entities/entity.ts
- Never take or return a raw id. Wherever the generated endpoint takes an id,
the SDK takes the entity handle and reads
.idoff it internally; wherever it returns an id, the SDK returns a handle. If the id refers to a noun that has no entity class yet, build that entity (and its namespace) as part of this change โ do not fall back to astringparameter. The one exception is astatic byId, which is how handles are minted in the first place. - Match the conventions:
static byId(client, id); detail viaLazy+protected fetch(), exposed withthis.field(...)/this.mappedField(...)- extend
MacroEntity/FavoritableEntity/PropertiedEntity, settingentityType/propertyEntityType - writes touching this entity's detail โ
this.mutate(...); others โunwrap(...) - every generated call goes through
unwrap() - cursor lists โ
paginate()โAsyncGenerator; search โentitySearch(...) - camelCase +
undefinedon the SDK side even when the wire is snake_case/nullable - TSDoc every public member. No
any.
- New service? Only when the endpoint's service isn't reachable from
MacroClientyet โ per service, not per entity; a new entity needs none of this. Wire theSdkintosrc/utils/client.ts, a host intosrc/config.ts, and an entry intoACCESSORSinsrc/coverage/check.ts. That last one is easy to miss: coverage decides "is this called?" by greppingsrc/for the literal text.<accessor>.<endpoint>(, so a service absent fromACCESSORSreports every one of its endpoints asUNCOVEREDno matter how well you wrapped them. If a whole service looks uncovered, check that map before believing it. - Already listed as skipped? Remove it, or coverage fails with
STALE SKIP. - Verify:
just check && just coverage && bun run lint && bun run format. - Document: README only for genuinely new user-facing capability. New webhook
events come from the storage spec via
src/events/types.tsโ regenerate, never hand-write.
Never
- Hand-edit
generated/orspecs/โ build output. - Accept an id where an entity handle belongs.
- Mark something skipped just to make coverage pass.