Release notes
Auto-generated from
CHANGELOG.mdat build time. The repo file is canonical.
All notable changes to @adonisjs-lasagna/saas-tenancy are documented here.
This project adheres to Semantic Versioning.
[0.2.2] — 2026-06-01
Feature and hardening release. Adds a dependency-resilience degradation policy and a billing webhook replay fallback, closes several edge-case failure modes found in a second audit, and activates the coverage gate. No breaking changes: every new behavior is off by default or preserves the prior contract. Now 555 unit + 358 integration + 123 e2e.
Added
- Dependency resilience policy.
ResilienceService.run()is one typed, observable contract for what happens when a backing dependency (Redis, Postgres, Stripe) is unavailable:fail-openreturns a fallback,fail-closedthrowsDependencyUnavailableException(503 +Retry-After). Configure it per dependency underconfig.resilience, and every degradation can emit aDependencyDegradedevent plus an OpenTelemetry span event for alerting. Adopted inQuotaServiceandRateLimitMiddleware. - Billing replay past Stripe's retrieval window. When Stripe reports an event is gone (
resource_missing),BillingService.retrieveEvent()reconstructs it from a PII-free, structurally-faithful copy the webhook controller persists instripe_processed_events.payload(toReplayablePayload), sotenant:billing:replayworks on events older than Stripe's ~30-day window. - Reference docs. New Configuration, Exceptions, Troubleshooting, and Resilience pages on the docs site.
Fixed
- Circuit breaker state survives a restart. Persisted OPEN state is now restored from Redis on process start, so a known-down tenant DB fails fast across a deploy instead of being probed back to life.
- Unified Redis-outage handling in quotas.
QuotaService.consume/trackroute through the resilience policy, ending the silentreturn 0and the raw ioredis throw on a Redis outage. - Smaller correctness and hardening fixes:
SchemaPgDriverlogs evicted connection-release failures instead of swallowing them;assertSafeIdentifierguards the backup/restore schema name; dead cache key removed from the feature-flag service;SqlImportServicelazy-loads its logger so it is unit-testable;reportUsageidempotency-key JSDoc corrected.
Changed
- Coverage gate is live.
check-coverageis enforced on the unit run (test:coverage). The integration coverage run is report-only because it executes the compiledbuild/and c8 does not attribute that execution back tosrc. - Test suite grew to 555 unit + 358 integration + 123 e2e.
[0.2.1] — 2026-05-17
Hardening release. Three production-affecting bug fixes uncovered by a test-coverage audit, plus a substantial integration + E2E expansion (now 505 unit + 355 integration + 123 e2e).
Fixed
- Package queue jobs are now resolvable by
queue:work.MultitenancyProvider.boot()registersInstallTenant,UninstallTenant,CloneTenant,BackupTenant,RestoreTenant,ProcessStripeEventJob,BillingCleanupJob, andReportUsageBatchJobwith@adonisjs/queue'sLocator. Pre-fix, host apps' job auto-discovery (app/jobs/**) didn't reach intonode_modules, so any dispatched package job was dead-lettered at the worker. Jobs are registered (and dispatched) underlasagna.<JobName>so a host app's same-named job can't collide. CloneServiceinteger-sequence reset now actually runs.#resetIntegerSequenceswas passing$1/$2bindings totrx.rawQuery(); Knex rejects those (it expects?), but the failure was swallowed by the surrounding savepoint rollback. Result: every cloned tenant inherited its source's sequence, so the next insert PK-collided with a copied row. Identifiers are now interpolated directly into the SQL (guarded byassertSafeIdentifierupstream).ImpersonationMiddlewareconstructor no longer breaks IoC resolution. The optional typed constructor parameter forced the AdonisJS container to try injectingImpersonationServiceat middleware resolution time, which it can't (the service needs a config-validated boot). Refactored to aprotected getService()seam that subclasses can override for tests.
Changed
- Build artefact no longer embeds TypeScript source.
inlineSources: trueremoved fromtsconfig.json..js.mapfiles inbuild/still reference.tspaths for stack traces but no longer carry the full source bytes — smaller install footprint for consumers. - Test coverage tooling. Added
c8withtest:coverage/test:integration:coveragescripts and.c8rc.json(thresholds report-only at 0; ratchet after a baseline is captured). CI uploadslcov.infoas an artifact. - CI provisions MinIO + mock-oauth2-server so the new S3 / OIDC integration specs run against real backends. Optional
STRIPE_TEST_API_KEYsecret enables the Stripe live-API smoke test; without it the spec reports itself skipped.
Test coverage
Closing the gap between "lots of tests" and "production confidence". New end-to-end coverage for previously-thin paths:
examples/api/tests/e2e/commands_lifecycle.spec.ts(10 tests) — real ace command execution fortenant:list/suspend/activate/import/purge-expired/maintenance/impersonate/backups:run/webhooks:retry.examples/api/tests/e2e/queue_jobs.spec.ts(2 tests) — realqueue:worksubprocess provisioning and tearing down tenants.tests/integration/services/backup_s3.spec.ts(real MinIO),sso_oidc_real.spec.ts(realmock-oauth2-server),stripe_real_smoke.spec.ts(real Stripe test API).tests/integration/middleware/rate_limit.spec.ts(6 tests, real Redis pipeline) andimpersonation_middleware.spec.ts(real HTTP + Redis).tests/integration/services/bootstrapper_isolation.spec.ts(9 tests, cross-tenant isolation + 16-wayAsyncLocalStorageconcurrency),clone_service.spec.ts,doctor_checks_real.spec.ts,telemetry_export.spec.ts(with real OTel SDK +AsyncLocalStorageContextManager).tests/integration/billing/diagnostics_commands.spec.ts(tenant:billing:doctor+tenant:billing:test-webhook).tests/integration/billing/stripe_real_smoke.spec.tsexpanded to cover every Stripe SDK call-site.
Upgrade notes
- Drain your queue before upgrading if you have unprocessed package jobs in Redis under the old names (
InstallTenant,UninstallTenant, etc.). After upgrade, the worker resolves them underlasagna.<JobName>, so any pending pre-upgrade jobs will dead-letter at the worker. - Subclassers of
ImpersonationMiddleware: the constructor parameter is gone. OverridegetService()instead of injecting vianew ImpersonationMiddleware(service).
[0.2.0] — 2026-05-09
Adds the Stripe billing satellite as the ninth opt-in feature. Opt in with node ace configure @adonisjs-lasagna/saas-tenancy --with=billing and npm install stripe@^18.
Added
- Billing satellite — full Stripe integration:
- Idempotent webhook receiver (
POST /webhooks/stripe) with rawINSERT ... ON CONFLICT (event_id) DO NOTHING. HMAC-SHA256 signature verification. Optional IP allowlist supporting both literal and CIDR entries vianode:net.BlockList(zero deps). - Configurable dunning state machine (
maxAttempts,action,gracePeriodDays) matching Stripe Smart Retries. - Metered billing —
BillingService.reportUsage()plus theusageMappingauto-bridge that batchesQuotaTrackedevents into a singleReportUsageBatchJobper(tenant, meter). - Checkout (
createCheckoutSession) and Billing Portal (createBillingPortalSession) helpers. - Tenant hard-delete policy (
onTenantDelete:cancel/detach/preserve) wired viaHookRegistry.beforeDestroy.
- Idempotent webhook receiver (
- 6 ace commands:
tenant:billing:sync,tenant:billing:backfill,tenant:billing:replay,tenant:billing:cleanup,tenant:billing:doctor,tenant:billing:test-webhook. - 10 events:
SubscriptionActivated,SubscriptionUpdated,SubscriptionCanceled,SubscriptionPaused,SubscriptionResumed,TrialEnding,PaymentSucceeded,PaymentFailed,BillingMisconfigured,BillingEventDeadLettered. - 3 jobs:
ProcessStripeEventJob,ReportUsageBatchJob,BillingCleanupJob. - 4 satellite tables:
stripe_customers,stripe_subscriptions,stripe_processed_events,stripe_meter_events. Plustenant_plans(shared with the quotas satellite). - Health check
billingHealthCheck(Stripe API ping + webhook freshness;SLOW_API_THRESHOLD_MSexported for test tuning). - Testing helpers under
@adonisjs-lasagna/saas-tenancy/testing:MockStripe(in-memory SDK double) andsignWebhookPayload. - PII redaction:
redactStripeEvent()strip-list (whitelist) for webhook payloads and structured logs.BillingEventDeadLetteredcarries a stableerrorCodeenum, never rawerror.message. - Documentation: full reference at Billing satellite; end-to-end recipe at Stripe + quotas (cookbook).
Changed
- Total ace command count is now 33 (was 27).
- Total typed events is now 23 (13 tenant lifecycle + 10 billing).
- Total bundled jobs is now 8 (5 tenant + 3 billing).
- Boot guard:
BillingService.verify()now throws when ask_live_*key is loaded outside production withoutSTRIPE_ALLOW_LIVE_IN_DEV=true(was warn-only). Same hard-fail in the other direction (sk_test_*+NODE_ENV=production). multitenancy.stubadds/webhooks/stripeto the defaultignorePaths. Hosts that changewebhook.pathmust updateignorePathsaccordingly.
[0.1.0] — 2026-05-07
Initial release of @adonisjs-lasagna/saas-tenancy.
This package continues the work previously published as @adonisjs-lasagna/multitenancy v2.x. The rename reflects the positioning of the package as the SaaS-tenancy foundation for AdonisJS 7. The codebase is the same hardened core: pluggable isolation drivers (schema-pg, database-pg, rowscope-pg, sqlite-memory), 13 typed lifecycle events, eight satellite features, contextual logging, scheduled backups, read-replica routing, the tenant:doctor diagnostic command, and an admin REST API.
Highlights
- Schema isolation — every tenant gets its own
tenant_<uuid>PostgreSQL schema, provisioned and routed automatically. - Pluggable isolation — schema-per-tenant, database-per-tenant, shared-with-row-scope, or in-memory SQLite for tests.
- Lifecycle hooks + 13 typed events — declarative
before/afterhooks wired into commands and jobs. - Contextual logging —
tenantIdrides through HTTP and queue jobs viaAsyncLocalStorage. tenant:doctor— ten built-in checks,--fixfor auto-recovery,--jsonfor CI,--watchfor a live TUI.- Plans & quotas — declarative plans, atomic rolling counters, snapshot usage,
enforceQuota()middleware that returns 429. - Scheduled backups + retention — tier-based intervals, S3 mirror with purge awareness.
- Health probes + Prometheus —
/livez,/readyz,/healthz,/metrics. Noprom-clientpeer dep. - Read replica routing — round-robin, random, or sticky-by-tenant-id.
- REST admin API — 36 endpoints + OpenAPI 3.1 spec + Swagger UI.
- Soft delete TTL —
--keep-schemaon destroy,tenant:purge-expiredon a cron. - Eight satellites — audit logs (append-only at SQL level), webhooks (HMAC-signed + retries), quotas, feature flags, branding, SSO/OIDC, metrics, impersonation. All optional.
History
Pre-rename history (v1.x and v2.0.0-beta.x of @adonisjs-lasagna/multitenancy) lives at the prior repository: github.com/Arcoders/Adonisjs-Lasagna-Multitenancy.