backend/packages/ holds twenty packages in two scopes.

Two scopes

The ported packages kept their @repo/* names so their internal imports did not have to change. pnpm does not care about the scope prefix.

All packages

Dependency direction

Rules that follow from it:
  • Packages never import from apps/core-api. When a package needs something the app owns, the app passes it in or the package publishes an event the app listens to. publishSubscriptionChange in @repo/payments is the example.
  • @repo/auth reaches core-api’s domain tables through @repo/database’s client (for staff seats and studio archiving), and reaches core-api’s HTTP API through a signed request (provisionCoach).
  • @perform/types and @repo/logs are at the bottom and import nothing from the workspace.

Building

Turbo builds packages in dependency order ("dependsOn": ["^build"]), and typecheck and test also depend on ^build.
Running a package’s own script with --filter (for example pnpm --filter @perform/core-api typecheck) calls tsc directly and does not build dependencies first.

Stale builds

@repo/* packages are consumed from dist. A source change in one is invisible to core-api until the package is rebuilt. Typical symptoms after pulling: @perform/* packages resolve to source for types but to dist at runtime (their package.json main points at dist), so they need a build before node dist/server.js or a tsx run sees a change.

Conventions for packages

  • A package exports a barrel from src/index.ts. Consumers import the package name, never a deep path.
  • @perform/* packages take configuration as arguments and have no side effects at import.
  • Some @repo/* packages do have import side effects. @repo/database creates its Prisma singleton and throws if DATABASE_URL is unset. @repo/api’s backend-config.ts validates env at import. Keep that in mind when importing one in a test.
  • Dependency versions are pinned exactly in the ported packages.

Adding a package

1

Create the folder

packages/<name>/ with package.json, tsconfig.json and src/index.ts. Copy the config from a neighbour in the same scope. New code written for the API belongs in @perform/*.
2

Set the entry points

"type": "module", and main, types and exports pointing at dist.
3

Add it as a dependency

In the consumer’s package.json as "workspace:*", using the pnpm CLI.
4

Build once

pnpm install, then pnpm --filter <package> build.