feat(session): persistent browser context pool with lifecycle management

- SessionContextService: in-memory Map of live Playwright handles, reused
  per sessionName across exec_code/open_url calls; closed on module destroy
- SessionSchedulerService: @Interval closes idle sessions and deletes old
  closed ones using SESSION_IDLE_TIMEOUT_MINUTES / SESSION_DELETE_CLOSED_DAYS
- SessionService: onApplicationBootstrap closes all open sessions on restart;
  upsert marks status=open and sets lastUsedAt; adds markOpen/markClosed/
  touchLastUsed/findExpiredOpen/findOldClosed/findById helpers
- SessionEntity: status (open|closed) and lastUsedAt columns added
- AuthService: keeps browser alive after login, registers context in pool
- BrowserService: named sessions reuse persistent context; anonymous remain ephemeral
- AppConfig: all config fields declared with typed defaults; validate wired
  into ConfigModule so mis-configuration fails fast at startup
- ConfigService<AppConfig, true> used everywhere — no more untyped get() calls
This commit is contained in:
2026-04-08 20:14:05 +03:00
parent b2edac062f
commit 9e79cad237
14 changed files with 424 additions and 86 deletions
+6 -1
View File
@@ -45,11 +45,16 @@ import { ScenarioRunLogEntity } from "../src/scenario/scenario-run-log.entity";
import { HealthController } from "../src/health/health.controller";
import { HttpExceptionFilter } from "../src/filters/http-exception.filter";
import { LoggingInterceptor } from "../src/interceptors/logging.interceptor";
import { validateAppConfig } from "../src/config/app.config";
export async function buildTestApp(): Promise<INestApplication> {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({ isGlobal: true, ignoreEnvFile: true }),
ConfigModule.forRoot({
isGlobal: true,
ignoreEnvFile: true,
validate: validateAppConfig,
}),
TypeOrmModule.forRoot({
type: "better-sqlite3",
database: ":memory:",