Files
netris-nestri/packages/core
Wanjohi 64a90abf75 fix(api): a misshapen id is bad input, not a server fault
Ids are stored in a fixed-width column, so an overlong one is refused by
Postgres rather than simply matching nothing. That refusal is not a foreign-key
violation, so it fell through to the global error boundary and reached the
caller as a 500 — telling a host to retry something that can never succeed.
Measured: a 44-character user id returned 500, where an absent but well-formed
one correctly returned 404.

`Identifier.schema` is the natural place for the check and had no callers yet,
so it now asserts the exact width an id has as well as its prefix — including
the separator, without which `usrsomething` reads as a user id. The enrolment
schema uses it for both foreign keys, so the refusal happens where the input
arrives and names the field.

Also index `steam_enrolment.user_id`. The primary key begins with the machine,
which answers what one host holds and nothing else, so neither of the two
things that read by user alone can use it: the cascade behind deleting a user,
and asking which hosts hold a token for one person. The table's migration has
not been released, so this is folded into it rather than following it with a
correction.
2026-09-06 13:51:20 +03:00
..
2026-08-06 22:13:51 +03:00
2026-08-06 22:32:33 +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_...');