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
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
55 lines
2.0 KiB
TypeScript
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 {}
|