# Migration Guide: 11.21.0 → 11.21.1

## Overview

| Category | Changes | Effort |
|----------|---------|--------|
| **Breaking Changes** | None | None |
| **New Features** | Tenant membership cache with configurable TTL, cache invalidation APIs, DB indices for BetterAuth | Low (opt-in) |
| **Performance Improvements** | CheckSecurityInterceptor, processDeep, EmailService, RequestContext bypass | None (automatic) |
| **Migration Effort** | Update package version | ~1 minute |

---

## Quick Migration

This is a PATCH release with no breaking changes. All improvements are backward compatible and most activate automatically.

```bash
# Update package
pnpm add @lenne.tech/nest-server@11.21.1

# Verify build
pnpm run build

# Run tests
pnpm test
```

No code changes required.

---

## What's New in 11.21.1

### 1. Tenant Membership Cache (`multiTenancy.cacheTtlMs`)

The `CoreTenantGuard` now caches membership lookups in-process to avoid repeated database queries when the same user accesses the same tenant. This is especially beneficial for high-traffic multi-tenant applications.

**Default behavior:** Cache is enabled with a 30-second TTL. In test environments (`VITEST=true`, `NODE_ENV=test`, or `NODE_ENV=e2e`), the cache is automatically disabled to avoid stale data between test cases.

```typescript
// config.env.ts — default (no changes needed, cache is on)
multiTenancy: {}

// config.env.ts — custom TTL (e.g., 60 seconds)
multiTenancy: {
  cacheTtlMs: 60_000,
}

// config.env.ts — disable cache entirely
multiTenancy: {
  cacheTtlMs: 0,
}
```

**Important for horizontally scaled deployments:** The cache is process-local. When a membership is added or removed on one server instance, other instances will not see the change until the TTL expires. For security-critical deployments where immediate revocation is required, reduce the TTL or set it to `0`.

### 2. Cache Invalidation APIs

Two new public methods on `CoreTenantGuard` allow programmatic cache invalidation:

| Method | Description |
|--------|-------------|
| `invalidateUser(userId)` | Clears all cached entries for a specific user |
| `invalidateAll()` | Clears the entire membership cache |

These are called automatically by `CoreTenantService` whenever memberships are added, removed, or updated. **No action required** unless your project extends `CoreTenantService` with custom membership mutation methods.

**Action required only if:** You have a custom service that modifies tenant memberships directly (bypassing `CoreTenantService`). In that case, inject `CoreTenantGuard` and call `invalidateUser()` after changes:

```typescript
import { CoreTenantGuard } from '@lenne.tech/nest-server';

@Injectable()
export class CustomTenantService extends CoreTenantService {
  constructor(
    // ... other deps
    private tenantGuard: CoreTenantGuard,
  ) {
    super(/* ... */);
  }

  async customMembershipChange(userId: string, tenantId: string): Promise<void> {
    // ... your custom logic ...
    this.tenantGuard.invalidateUser(userId);
  }
}
```

### 3. User Role Cache Invalidation (`CoreBetterAuthUserMapper`)

A new public method `invalidateUserCache(iamId)` is available on `CoreBetterAuthUserMapper`. It clears the cached user data (15-second TTL) used during session-to-user mapping.

This is called automatically from `CoreUserService.setRoles()`. **No action required** unless your project modifies user roles outside of `CoreUserService.setRoles()`.

**Action required only if:** You have custom logic that changes user roles directly in the database:

```typescript
import { CoreBetterAuthUserMapper } from '@lenne.tech/nest-server';

@Injectable()
export class CustomUserService {
  constructor(private userMapper: CoreBetterAuthUserMapper) {}

  async customRoleChange(userId: string, iamId: string): Promise<void> {
    // ... your custom role update logic ...
    this.userMapper.invalidateUserCache(iamId);
  }
}
```

### 4. Automatic Database Indices

The following indices are now auto-created by `CoreBetterAuthService.onModuleInit()` when BetterAuth is enabled:

| Collection | Index | Purpose |
|------------|-------|---------|
| `session` | `{ token: 1 }` | Fast session token lookups |
| `session` | `{ userId: 1, expiresAt: 1 }` | Active session queries per user |
| `users` | `{ iamId: 1 }` (sparse) | IAM ID lookups during session mapping |

No action required. Index creation is idempotent — calling `createIndex` on an existing index is a no-op. These indices improve query performance for BetterAuth session validation and user mapping.

---

## Performance Improvements

These optimizations apply automatically. No configuration or code changes needed.

### 1. CheckSecurityInterceptor: Conditional JSON.stringify

`JSON.stringify` on response data now only runs when `debug: true` is configured. Previously, every response was serialized even when debug output was not used. This reduces CPU overhead on every request that passes through the security interceptor.

### 2. processDeep: Options Object Reuse

The `processDeep()` helper (used by `CheckSecurityInterceptor` and input validation) now reuses its options object across recursive calls instead of recreating it for each nested property. This reduces garbage collection pressure for deeply nested response objects.

### 3. EmailService: SMTP Transport Reuse

The SMTP transport created by `nodemailer.createTransport()` is now cached as a singleton per configuration. Previously, a new transport was created for each email send operation. The cache key is derived from the SMTP configuration hash, so transport changes at runtime are handled correctly.

### 4. RequestContext.runWithBypass*: Skip Redundant Context

`RequestContext.runWithBypassRoleGuard()` and `RequestContext.runWithBypassTenantGuard()` now skip creating a new `AsyncLocalStorage` context when the bypass flag is already set. This avoids unnecessary object spread operations in nested bypass calls.

---

## Compatibility Notes

| Pattern | Status | Notes |
|---------|--------|-------|
| `multiTenancy: {}` | Compatible | Cache enabled at 30s TTL by default |
| `multiTenancy: { cacheTtlMs: 0 }` | Compatible | Cache disabled, same behavior as before 11.21.1 |
| No `multiTenancy` config | Compatible | No changes, no overhead |
| Custom `CoreTenantService` extensions | Compatible | Automatic invalidation if using `super.addMember()` etc. |
| Direct membership DB mutations | **Check** | Must call `invalidateUser()` manually (see section 2 above) |
| Custom role-change logic (bypassing `CoreUserService.setRoles`) | **Check** | Must call `invalidateUserCache()` manually (see section 3 above) |
| BetterAuth disabled | Compatible | DB indices are not created when BetterAuth is disabled |
| Test environments | Compatible | Cache auto-disabled in test/e2e environments |

---

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| Stale membership data after role change | Membership cache TTL not expired | Call `CoreTenantGuard.invalidateUser(userId)` after custom membership changes |
| Stale user roles after `setRoles()` | Should not happen (auto-invalidated) | Verify you are using `CoreUserService.setRoles()`, not direct DB updates |
| Membership changes not visible across server instances | Process-local cache | Reduce `cacheTtlMs` or set to `0` for immediate consistency |
| Tests see stale membership data | Cache enabled in test env | Verify `NODE_ENV=test` or `VITEST=true` is set (auto-disables cache) |

---

## Module Documentation

### Tenant Module

- **README:** [src/core/modules/tenant/README.md](../src/core/modules/tenant/README.md)
- **Integration Checklist:** [src/core/modules/tenant/INTEGRATION-CHECKLIST.md](../src/core/modules/tenant/INTEGRATION-CHECKLIST.md)
- **Key File:** `src/core/modules/tenant/core-tenant.guard.ts` — cache implementation and `invalidateUser()`/`invalidateAll()` methods

### BetterAuth Module

- **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md)
- **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
- **Key File:** `src/core/modules/better-auth/core-better-auth.service.ts` — DB index creation in `onModuleInit()`
- **Key File:** `src/core/modules/better-auth/core-better-auth-user.mapper.ts` — `invalidateUserCache()` method

---

## References

- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
- [11.20.x → 11.21.0 Migration Guide](./11.20.x-to-11.21.0.md) (previous release with multi-tenancy breaking changes)
