sdk-ts — the TypeScript client
Lemonfiber — sdk-ts
The TypeScript client for lemonfiber's local web API. Typed calls, a typed
event stream, and a typed error — published as
@lemonfiber/sdk-ts.
Status: unreleased. The typed calls, the event stream and the generated types are all in and exported; the package is not on npm yet. Full account in the spec:
30-repos/sdk-ts.md.
What it is
Section titled “What it is”A library with no user interface and no server. It speaks the
web API
and exposes it as typed calls, a typed event stream, and a typed error. It is the
only thing lemonfiber-web uses to
talk to the core, and the first thing any other consumer should reach for.
It is a peer of sdk-php, not its
original — both implement one specification, and neither is the reference for the
other. Where this client disagrees with the contract, this client is wrong.
Install
Section titled “Install”npm install @lemonfiber/sdk-tsRequires Node 26 or newer, or any modern browser. It has no runtime dependencies — a client library’s dependency tree becomes every consumer’s.
lemonfiber prints an address and a token when it starts serving. Pass both in;
the token is sent as a header and never placed in a URL.
import { Client, follow } from "@lemonfiber/sdk-ts";
const opened = Client.at({ url: "http://127.0.0.1:9000", // loopback only — anything else is refused token: printedByLemonfiber, sending: fetch,});if (!opened.ok) throw new Error(opened.problem.message);
const status = await opened.client.read("status");if (status.ok) { status.value.kind; // "status" status.value.data; // the payload, shaped by kind}
await opened.client.act("restart", { forms: ["tv"], services: ["sonarr"] });An action’s name and its arguments are the command line’s own. A name this surface does not offer is refused rather than invented, and a field no action takes is refused rather than ignored.
Live updates arrive as envelopes. Anything gathered before a break in the connection is marked out of date rather than shown as current:
for await (const arrival of follow({ url, token: printedByLemonfiber, fetching: fetch })) { if (arrival.at === "live") draw(arrival.kind, arrival.data); if (arrival.at === "stale") markOutOfDate(arrival.quietForMs); if (arrival.at === "lost") report(arrival.problem.message);}Nothing throws for an expected failure. A call returns either a value or a
Problem carrying a sentence written for a person to read.
problem.kind says which sort of refusal it was, so a caller need not read the
sentence to know what to do with it:
kind |
What it means |
|---|---|
missing |
lemonfiber has nothing by the name the request gave |
misasked |
It could not answer the request as it was asked |
failed |
It understood the request and its own answering failed |
refused |
The key this page is using is not the one this run expects |
unreachable |
Nothing lemonfiber wrote came back at all |
refused is the key and nothing else. A caller reading it may ask for a new key
without reading the sentence, which is the point of a kind — and a caller reading
failed may not, because what failed is behind the answer rather than in front of
it. A stopped container engine is failed, and asking again once it is running
will succeed.
missing, misasked and failed always carry lemonfiber’s own sentence: a body
this package cannot read is reported as unreachable whatever status carried it,
so a page from something standing in front of lemonfiber is never passed off as its
account of what there is.
src/generated/ is not yours to edit
Section titled “src/generated/ is not yours to edit”Everything under src/generated/ is written by
npm run contract:generate from the vendored
contract/web-api.contract.json, which lemonfiber produces from the
Rust types that actually serialise the reply. Never edit it by hand — a
hand-written response shape is a second source of truth for the contract, which
ARCH-R58 forbids.
A change belongs in those Rust types; everything downstream follows from that. The
contract:check gate regenerates and diffs, so a hand edit fails CI rather than
merging:
npm run contract:sync # pull a newer contract from lemonfibernpm run contract:generate # rewrite src/generated/ from itWhy it works this way: ADR-0014.
The gate
Section titled “The gate”Everything CI runs, in one command:
npm cinpm run ciThe individual steps are the scripts in package.json, and each
runs on its own while you work — npm test for the fast loop.
npm ci is also what turns on this repository’s pre-push hook, which refuses a
push that would leave a branch carrying no commit origin/main does not — what
pushing the trunk over a feature branch looks like. npm’s prepare script does
it, so npm install serves too. A clone nobody has installed into has no hook:
it is git config core.hooksPath .githooks, per clone, and git cannot read
.githooks/ on its own.
The bar is the Rust workspace’s, in its TypeScript equivalents: 100% coverage
across lines, statements, branches and functions; strict with
noUncheckedIndexedAccess and exactOptionalPropertyTypes; typescript-eslint
strictTypeChecked with zero warnings tolerated. There are no escape hatches, and
a threshold is not a target to negotiate.
Two version numbers
Section titled “Two version numbers”They do different jobs, and conflating them is the mistake to avoid. The
package carries semver. api_version is a monotonic integer describing the
wire, and CONTRACT_API_VERSION is the one this package speaks. Many package
versions may speak one wire version. A reply in a version it cannot speak is
refused, naming both (ARCH-R55).
Releases are recorded in CHANGELOG.md, generated from the commit history rather than kept by hand.
Contributing
Section titled “Contributing”The spec is canonical: every change cites a spec identifier that already
exists. Routine maintenance cites GOV-R12.
- Contributing guide · Support · Security · Code of conduct
- ADR-0013 — why the SDK exists and is separate
- The full spec page for this repo
Licence
Section titled “Licence”Hippocratic License 3.0 — ethical-source, source-available, deliberately not OSI-approved. See the rationale.
This page lives in another repository Rendered from lemonfiber/sdk-ts at af34d9c, 2026-09-09. Read the source of this page