@perform/types has no dependencies. @perform/errors depends on it and on Express and Zod types.
@perform/types
packages/types/src/index.ts. Constants are as const objects with a union type of the same name, not TypeScript enums.
ErrorCode
Response types
/v1 envelope. Client code can import them to type responses.
StudioRole
req.auth. See Roles and permissions.
Staff permission groups
organizationRoleForStaff returns admin when the permission group is super_admin or the coach role is HEAD_COACH, otherwise member. @repo/auth uses it when it creates the better-auth membership for a staff member. It lives here because both the auth package and core-api need the same answer.
STUDIO_ONBOARDING_WINDOW_MS
72 hours in milliseconds. How long after its creation a studio that the /start wizard bootstrapped still counts as in the wizard when its owner never finished it. Plan limits exempt the wizard’s own invites and import during that window. Read by billing.limits.ts in core-api and by studio-lifecycle.ts in @repo/auth.
@perform/errors
packages/errors/src/index.ts.
AppError
Default status per code:
Examples from the codebase:
AppError extends Error, so instanceof AppError works across the workspace as long as there is one copy of the package, which the workspace guarantees.
toApiError(err, requestId?)
Turns anything thrown into an ApiError body. It is what the middleware sends, and it is exported so other code can build the same body. The order of checks:
- An
AppError, or an error thatmapPrismaErrorormapBodyParserErrorcan turn into one.detailsis copied when present. - A
ZodError: codeVALIDATION, messagevalidation failed,detailsas{ path, message }per issue. - Anything else: code
INTERNAL, messageinternal server error.
mapPrismaErrormatches onerr.nameanderr.code, so it does not need to import Prisma.mapBodyParserErrormatches errors that have a stringtype,expose === trueand a numeric 4xxstatus.isConnectionErrortreats messages containingconnection terminated,connection closed,econnrefused,connection refusedorconnection resetas a database outage.
errorMiddleware
AppError.status, 422 for a ZodError, else 500), logs one line with console.error for 5xx or console.warn for 4xx, and sends toApiError(err, requestId).
Register it last. It must have the four argument signature Express uses to recognize an error handler, which the exported constant already has.
notFoundMiddleware
Answers 404 with route not found: METHOD /original/url. Register it after every router and before errorMiddleware.
Using them outside core-api
A new Express service in the workspace gets the same envelope by registering the two middlewares and throwingAppError:
AppError and read code, status and details from it.
Adding an error code
- Add the key to
ErrorCodein@perform/types. - Add its status to
STATUS_BY_CODEin@perform/errors. The record type is keyed byErrorCode, so the compiler reports a missing entry. - Check the automation lane’s
STATUS_BY_ERROR_CODEmap and add the code there if it should not fall through to 400. - Rebuild both packages.
- Tell the web and mobile teams. Clients switch on
code, and an unknown code usually falls into a generic error path.
details.reason on an existing code over a new code. A new sub reason is backward compatible for clients. A new top level code is not.