mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 14:12:39 +02:00
Phase 1 of decision 0085, the api piece (see .nestri contract
window-streams).
A box run (session) now holds launches: one game or app each. Billing,
allowance and the free weekend still count sessions; nothing about
limits changes. session_box_active_unique stays.
- Migration 0027: launch table (id lch_..., session_id, game_id, state
requested|running|ended|failed, first, time_started, time_stopped,
time_stop_requested, end_reason, error_message). Backfills a first
launch for every run that has not stopped.
- POST /session: same request; the response data gains launches (the
first launch).
- POST /session/{id}/launch {gameId}: 201 with the launch. Same library
check as POST /session. 409 unless the run is starting or live and not
being stopped.
- POST /session/{id}/launch/{launchId}/stop: marks the launch; host gets
launch.stop until it reports it over. A launch in a run no host has
claimed ends at once.
- POST /session/{id}/launch/{launchId}/state (host, run's claimToken):
running | ended | failed, optional endReason/errorMessage.
- When the last launch is over, the run is stopped through the same path
as POST /session/{id}/stop. A run ending ends every launch in it.
- GET /session/{id} gains launches.
- GET /machine/jobs: session.start gains launchId (the first launch);
new kinds launch.start and launch.stop. Agents skip unknown kinds, so
old hosts behave as before.
Tests: apps/api 203 pass (new test/launch.test.ts), packages/core 256
pass, against the local test Postgres.
apps/api
The public HTTP API for Nestri — a Hono app. One handler, run either as a Cloudflare Worker or as an ordinary HTTP server.
What it does
Exposes the JSON API consumed by frontends and other clients. Every route is a thin wrapper that
validates input, delegates to a domain function in @nestri/core,
and returns { data: ... }. All business logic lives in the core package.
Routes:
| Prefix | Purpose |
|---|---|
/ |
Health check |
/user |
Current user profile, fingerprints, linked accounts |
/steam |
Link / sync / unlink a Steam account |
/library |
Owned games with playtime |
/games |
Game catalog |
/pairing-code |
Device pairing codes |
/machine |
Host machines |
/access-token |
Short-lived access tokens |
/doc |
Generated OpenAPI spec |
Structure
app/
index.ts # The handler: middleware, routes, error handler, /doc
server.ts # The same handler behind a listening socket
middleware/auth.ts # Bearer JWT, access token or host credentials → Actor
routes/*.ts # Thin route namespaces (UserApi, SteamApi, ...)
utils/ # ErrorResponses, Result(), validator wrapping
wrangler.jsonc # Worker configuration, one environment per stage
Dockerfile # The container, built from the repository root
test/ # Route tests
Key details
- Auth:
Authorization: Bearer …, carrying either a session token verified against@nestri/author a personal access token resolved from the database; or a registered host's ownx-nestri-machine-idandx-nestri-machine-secret. There is no shared secret and no credential that stands for more than one caller, so every route resolves to a specific user or a specific host — which is what lets a route say "the caller's own library" and mean it. - Errors: centralized
VisibleError→ typed JSON responses. - Settings arrive as bindings or as environment variables, and two of them have one spelling of
each: Postgres is
HYPERDRIVEorDATABASE_URL, and the route to the issuer is anAUTHservice binding orAUTH_INTERNAL_URL. Nothing here branches on which it got. AUTH_ISSUER_URLis the issuer's public URL and never the internal one, because it is compared literally against theissclaim on every token.
Running
bun run dev # under the Workers runtime, on :3000
bun run serve # as a plain process, on $PORT (default 3000)
Needs a Postgres database and a reachable issuer. Full list and deployment steps:
docs/deploy.md.