dorfteich/apps/api/src/app.module.ts
Claude Opus 4.8 acd1cc32b7
Some checks failed
CD / Promote to Int (push) Blocked by required conditions
CD / Build and push images (push) Successful in 2m56s
CI / Lint, typecheck, test (push) Failing after 1m17s
CI / Auth e2e pack (push) Successful in 2m24s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Has been cancelled
Add Yjs update-log compaction job (#40)
Update logs grow with every edit; compaction bounds storage and load time.

- prisma: `collab_open_sessions` table (page_id, heartbeat_at) — the
  live-session registry that lets the compaction job avoid pages being
  edited, decoupled from collab (no api↔collab network call) and self-
  healing (a crashed collab's rows age out of the freshness window).
- collab: `PostgresSessionRegistry` marks a page open on document load and
  closed on unload, and refreshes an every-30s heartbeat for all open docs;
  wired into the server hooks and started/stopped in index.ts.
- api: `CompactionService` runs hourly via the shared scheduler (#31). For
  pages with > 500 log rows and no fresh session it merges `page_updates`
  into `ydoc_state` and deletes the merged rows in one FOR UPDATE
  transaction — atomic, so a mid-run crash leaves the page untouched and the
  next run resumes. Content is unchanged (merged state = base + all updates),
  so the content cache is left as-is; updated_at is deliberately not bumped.
  Metric log line with pages compacted / rows / bytes removed.

Tests: DB-backed compaction test (content hash unchanged, below-threshold
skipped, active session skipped then picked up next run, stale heartbeat
ignored, idempotent); session-registry DB test (open/close, heartbeat
refresh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 08:15:55 +02:00

55 lines
2.0 KiB
TypeScript

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { LoggerModule } from 'nestjs-pino';
import { AdminModule } from './admin/admin.module';
import { AuthModule } from './auth/auth.module';
import { ApiExceptionFilter } from './common/api-exception.filter';
import { CompactionModule } from './compaction/compaction.module';
import { AppConfig } from './config/app-config.service';
import { ConfigModule } from './config/config.module';
import { FilesModule } from './files/files.module';
import { HealthModule } from './health/health.module';
import { MailModule } from './mail/mail.module';
import { PagesModule } from './pages/pages.module';
import { PondsModule } from './ponds/ponds.module';
import { PrismaModule } from './prisma/prisma.module';
import { RateLimitModule } from './rate-limit/rate-limit.module';
import { SettingsModule } from './settings/settings.module';
import { TrashModule } from './trash/trash.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [
ConfigModule,
PrismaModule,
RateLimitModule,
MailModule,
SettingsModule,
UsersModule,
PondsModule,
PagesModule,
FilesModule,
TrashModule,
CompactionModule,
AuthModule,
AdminModule,
LoggerModule.forRootAsync({
inject: [AppConfig],
useFactory: (config: AppConfig) => ({
pinoHttp: {
level: config.env.LOG_LEVEL,
// Human-readable logs in local development, JSON everywhere else.
transport: config.env.NODE_ENV === 'development' ? { target: 'pino-pretty' } : undefined,
autoLogging: config.env.NODE_ENV !== 'test',
// Request bodies are never logged (operations.md logging rules).
redact: { paths: ['req.headers.authorization', 'req.headers.cookie'], remove: true },
},
}),
}),
HealthModule,
],
providers: [{ provide: APP_FILTER, useClass: ApiExceptionFilter }],
})
export class AppModule {}