Skip to content

Exception reference

All exceptions extend AdonisJS's Exception, so each carries a stable status and code. Import them from the /exceptions subpath to catch by type:

ts
import {
  TenantNotReadyException,
  QuotaExceededException,
  DependencyUnavailableException,
} from '@adonisjs-lasagna/saas-tenancy/exceptions'

AdonisJS renders the status automatically. Catch by type (or match on code) when you want a custom response.

Reference table

ExceptionStatusCodeThrown when
MissingTenantHeaderException400E_MISSING_TENANT_HEADERNo tenant id could be resolved from the request (and none from tenancy.run()).
TenantHeaderDomainMismatchException400E_TENANT_HEADER_DOMAIN_MISMATCHA header-supplied tenant id contradicts the host or custom domain, a possible hijack attempt.
TenantNotFoundException404E_TENANT_NOT_FOUNDThe resolved tenant id doesn't exist in the repository.
CentralRouteViolationException404E_CENTRAL_ROUTE_VIOLATIONA central-only route was reached in a tenant context (or vice-versa).
TenantSuspendedException403E_TENANT_SUSPENDEDThe tenant exists but is suspended.
TenantNotReadyException503E_TENANT_NOT_READYThe tenant is still provisioning, so its schema isn't ready yet.
TenantMaintenanceException503E_TENANT_MAINTENANCEThe tenant is in maintenance mode. Carries retryAfterSeconds.
CircuitOpenException503E_CIRCUIT_OPENThe tenant's DB circuit breaker is OPEN, so it fails fast instead of hammering a down database.
RateLimitUnavailableException503E_RATE_LIMIT_UNAVAILABLEThe rate-limit backend (Redis) errored and the route is fail-closed.
DependencyUnavailableException503E_DEPENDENCY_UNAVAILABLEA fail-closed dependency (Redis/PG/…) errored inside ResilienceService.run(). Sets Retry-After. Carries dependency, operation, tenantId.
TooManyRequestsException429E_TOO_MANY_REQUESTSA request exceeded a RateLimitMiddleware window. Sets Retry-After.
QuotaExceededException429E_TENANT_QUOTA_EXCEEDEDQuotaService.consume() would exceed the plan limit. Carries quota, limit, current, attempted.
BillingException400E_BILLINGA Stripe/billing error. Carries a billingCode (see Billing) and isRetryable().

Handling patterns

Catch by type

ts
import { QuotaExceededException } from '@adonisjs-lasagna/saas-tenancy/exceptions'

try {
  await quotas.consume(tenant, 'apiRequests', 1)
} catch (err) {
  if (err instanceof QuotaExceededException) {
    return response.tooManyRequests({ quota: err.quota, limit: err.limit })
  }
  throw err
}

Degraded dependencies

When a fail-closed dependency is down, DependencyUnavailableException surfaces a clean 503 + Retry-After instead of a raw driver error. Pair it with the DependencyDegraded event for alerting:

ts
import emitter from '@adonisjs/core/services/emitter'
import { DependencyDegraded } from '@adonisjs-lasagna/saas-tenancy/events'

emitter.on(DependencyDegraded, ({ payload }) => {
  pager.warn(`dependency ${payload.dependency} degraded on ${payload.operation}`)
})

See Configuration → Resilience for choosing fail-open vs fail-closed per dependency.

Retry-After aware exceptions

TenantMaintenanceException, TooManyRequestsException, and DependencyUnavailableException all carry retry hints. Surface them so clients back off instead of busy-looping.

Was this page helpful?

Released under the MIT License.