Files
netris-nestri/packages/core
Wanjohi eee3727d0c feat: add a host by approving it in a browser, without pasting a token (#416)
The installer no longer needs a token copied from the dashboard. Run
bare, the host agent shows a link, a code and a QR; approving it in a
browser registers the machine.

**Issuer**
- New device client `installer`. Its access token lasts 15 minutes and
it gets no refresh token: a refresh lifetime of 0 now means none is
stored or returned.
- `clientTTL(clientID)` sets lifetimes per client and wins over what a
sign-in asks for. Desktop is unchanged.
- `/device/authorize` allows 10 requests per address per 10 minutes.
Past that it answers 429 `slow_down` with `Retry-After`.
- The confirm page for the installer reads "Add a computer as a Nestri
host?". The wording is fixed per client id.

**API**
- An `installer` token is accepted only on `POST
/machine/install-token`. Everything else answers 403
`insufficient_permissions`.
- Install tokens: at most 5 per person per hour, and at most 3 unspent
at once (429 `too_many_requests`).
- A team may own at most 10 machines. This is checked at mint and at
redeem under a row lock (429 `quota_exceeded`). A refused redeem leaves
the token unspent. Organisation hosts are not counted.

**install.sh**
- The token is optional. With none, `NESTRI_INSTALL_TOKEN` is left unset
and onboarding signs in.
- **Do not release this file until `DEFAULT_VERSION` points at a host
agent that can sign in.** Older agents need a token.

Tests: packages/core 256, apps/api 193, packages/auth 74, apps/auth 28,
all passing.
2026-10-16 20:49:02 +03:00
..
2026-08-06 22:13:51 +03:00
2026-08-06 22:13:51 +03:00

packages/core

@nestri/core — the domain layer for Nestri. All business logic, database access, and serialization lives here. The API and auth workers are thin pass-through translation layers on top.

What it contains

Area Files Purpose
db db/index.ts, db/types.ts, db/test.ts Drizzle + Postgres (Database.use/transaction), ULID column helpers
users user/* Users, linked accounts, fingerprints, library
teams team/* Teams + membership with roles (team_member)
games game/* Game catalog, depot content, per-host downloads
steam steam/index.ts Steam API integration & SSH identity resolution
auth auth/subjects.ts JWT subjects shared with the auth worker
infra env.ts, context.ts, actor.ts, fn.ts, id.ts, error.ts, examples.ts Environment, Actor model, zod-typed fn() wrappers, IDs, error types, examples
migrations migrations/ Drizzle-kit SQL migrations for Postgres schema

Conventions

  • Domain namespaces (user/, team/, ...) expose typed fn() functions that validate input with a Zod schema and serialize DB rows inside the function boundary — the API routes never see raw table rows.
  • Actor model: Actor.userID, Actor.type, ... pull the current authenticated identity from AsyncLocalStorage (set by the API middleware / auth worker) without passing it through call chains.
  • Soft delete: every table has time_deleted; queries filter with isNull(table.timeDeleted).
  • IDs: ULIDs via Identifier.ascending('user') → usr_....
  • Tables are defined in *.sql.ts files (drizzle) with namespaces in index.ts.
  • Environment is read through Env.get(), init by worker bindings.

Structure

src/
├── actor.ts, env.ts, id.ts, fn.ts, error.ts, examples.ts
├── db/
├── auth/
├── user/       (user.sql.ts, linked-account.*, fingerprint.*, library.*, index.ts)
├── team/       (team.sql.ts, member.*, index.ts)
├── game/       (game.sql.ts, depot.*, download.*, index.ts)
├── steam/      (index.ts)
├── pairing-code/
├── access-token/
└── machine/

Scripts

bun run db:push   # push schema (drizzle-kit)
bun run db        # open drizzle-kit

Usage

import { Team } from '@nestri/core/team/index';
import { Database } from '@nestri/core/db/index';

const team = await Team.fromID('tem_...');