Files
netris-nestri/packages/core
Wanjohi 15f8d3eb34 fix(core): hold the account rules when two requests arrive together
Three rules here are enforced across a lookup and then a write, and each
was only as good as whatever stopped the two from interleaving. Nothing
did.

The connection cap counted with `select ... for update` over the
connections a user already had. That locks the rows it finds, and when
it finds none it locks nothing — there are no gap locks under read
committed — so several first-time links all counted zero and all
inserted. Six concurrent links against a cap of four produced six. The
count now happens under a lock on the account's own row, which is the
one thing every caller for that account is guaranteed to contend on.

Creating an account from a verified address looked the address up and
then inserted. Two tabs finishing the same sign-in both found nothing,
and the loser got the driver's constraint violation instead of the
account the winner had just made. The unique index is the thing that
actually arbitrates, so the loser now reads back what the winner wrote.
Claiming an address on an older account had the same shape and now gives
the same sentence a screen would have shown a moment earlier.

The tests run each call several times at once against a real database,
because run one at a time all three pass whether or not any of this
exists.
2026-09-05 09:32:16 +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_...');