FAQ
General
Do nestarc packages depend on each other?
No. Every package can be installed and used independently. They compose well together via Prisma extension chaining, but it is not required.
Which NestJS versions are supported?
NestJS 10 and 11. Both are tested in CI.
Which Prisma versions are supported?
Prisma 5 and 6. Prisma 6 is integration-tested in CI; Prisma 5 is unit-tested.
Do you support both Express and Fastify?
@nestarc/safe-response supports both Express and Fastify out of the box. The other packages are HTTP adapter agnostic.
@nestarc/tenancy
RLS does not seem to be working
Most common causes:
- Connected as superuser — PostgreSQL superusers bypass RLS. Create a dedicated
app_userrole instead. - Missing FORCE ROW LEVEL SECURITY — Without it, table owners also bypass RLS. Run
ALTER TABLE ... FORCE ROW LEVEL SECURITY. - Missing tenant_id column — The table must have a
tenant_idcolumn, and thecurrent_settingkey in the RLS policy must match the extension configuration.
Run npx @nestarc/tenancy check to detect drift between your Prisma schema and SQL policies.
set_config does not work inside interactive transactions
By default, the Prisma extension uses batch transactions internally. set_config does not propagate into interactive transactions ($transaction(async (tx) => ...)).
Two solutions:
- Use the
tenancyTransaction()helper (recommended, works with all Prisma versions) - Enable
interactiveTransactionSupport: true(depends on Prisma internals)
See Installation for details.
How do I skip RLS for specific models?
Use the sharedModels option:
createPrismaTenancyExtension(tenancyService, {
sharedModels: ['Country', 'Currency'],
})Queries on shared models skip set_config and autoInjectTenantId.
How do I query without a tenant context?
Use withoutTenant() to explicitly clear the tenant context. Note that with RLS enabled, queries will return 0 rows. To query across all tenants, you need a separate admin connection that bypasses RLS.
@nestarc/safe-response
How do I disable response wrapping for a specific route?
Use the @RawResponse() decorator:
@Get('health')
@RawResponse()
healthCheck() {
return { status: 'ok' };
}Useful for file downloads, SSE, and health checks.
Is it compatible with class-transformer's @Exclude()?
Yes. Import SafeResponseModule before registering ClassSerializerInterceptor. Serialization runs first, then the result is wrapped.
@nestarc/soft-delete
Unique constraint conflicts after soft-delete
Multiple soft-deleted rows with the same value will break a standard unique constraint. Use a composite unique constraint instead:
model User {
id Int @id @default(autoincrement())
email String
deletedAt DateTime?
@@unique([email, deletedAt])
}Most databases treat NULL values as distinct in unique indexes, so uniqueness is enforced only among active records.
What is the maximum cascade depth?
Default is 3. Adjust with the maxCascadeDepth option.
@nestarc/feature-flag
What is the flag evaluation priority?
Flags are evaluated through a 6-layer cascade. The first matching layer wins:
- Archived (always false)
- User override
- Tenant override
- Environment override
- Percentage rollout
- Global default
How does percentage rollout work?
It hashes flagKey + userId (or tenantId) with murmurhash3 and takes the result modulo 100. The same user always lands in the same bucket, ensuring consistent results across requests.
@nestarc/audit-log
Can I modify or delete audit_logs records?
No. applyAuditTableSchema creates PostgreSQL rules that block UPDATE and DELETE on the audit table. This is by design to guarantee log integrity.
What is the difference between automatic tracking and manual logging?
- Automatic tracking: The Prisma extension detects CUD operations and records before/after diffs
- Manual logging:
AuditService.log()records business events (e.g., "invoice.approved") explicitly
Both write to the same audit_logs table.
@nestarc/pagination
Should I use cursor or offset pagination?
- Offset: Page numbers, total count display, admin dashboards
- Cursor: Infinite scroll, large datasets, real-time data with consistent results
Cursor mode activates automatically when after/before parameters are present.
What happens when a disallowed filter column is used?
An InvalidFilterColumnError (400) is thrown. Only columns and operators explicitly registered in filterableColumns are allowed. This is a security measure.