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.publishSubscriptionChangein@repo/paymentsis the example. @repo/authreaches 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/typesand@repo/logsare 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.
--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/databasecreates its Prisma singleton and throws ifDATABASE_URLis unset.@repo/api’sbackend-config.tsvalidates 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.