/
kochkatech
/
CorpGame
Обзор
Документация
Войти
/
kochkatech
/
CorpGame
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
dev
backend/src/prisma/prisma.service.ts
71 строка
4 KB
kochkareal
feat(devops): свод к одному процессу Node на SQLite, портативная сборка без Docker; аудит безопасности
06 авг 2026, 15:06
06 авг 2026, 15:06
3266bcd
Код
Авторство
О чём код?
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; import { existsSync, readdirSync } from 'node:fs'; import { migrationsDir as getMigrationsDir } from '../common/app-paths'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(PrismaService.name); async onModuleInit() { await this.$connect(); // journal_mode = WAL — читатель не блокирует писателя; без него дашборд с живой лентой // упирался бы в транзакции сбора ресурсов. // busy_timeout — если файл на секунду занял антивирус или бэкап, ждём, а не падаем // мгновенно с SQLITE_BUSY. // synchronous = NORMAL — партийной игре на ноутбуке не нужен fsync на каждый коммит. // // Побочный эффект: рядом с базой появляются corpgame.db-wal и corpgame.db-shm. Это // части одной базы — «сбросить игру» значит удалить папку data целиком, а не один файл. // // $queryRawUnsafe, а не $executeRawUnsafe: PRAGMA journal_mode отвечает строкой с новым // режимом, а $executeRawUnsafe в SQLite падает на любом результате // («Execute returned results, which is not allowed in SQLite»). // // PRAGMA действуют на соединение; journal_mode при этом пишется в заголовок файла и // переживает переподключение, остальные два — нет. Prisma держит для SQLite пул из // одного соединения, так что на практике этого достаточно. await this.$queryRawUnsafe('PRAGMA journal_mode = WAL;'); await this.$queryRawUnsafe('PRAGMA busy_timeout = 5000;'); await this.$queryRawUnsafe('PRAGMA synchronous = NORMAL;'); await this.assertSchemaIsCurrent(); } async onModuleDestroy() { await this.$disconnect(); } /** * Сверяет миграции, применённые к файлу базы, с теми, что приехали в сборке. * * Ловит сценарий «обновили app/, но оставили старую data/corpgame.db»: без этой проверки * он всплыл бы посреди мероприятия случайными «no such column». Данные одноразовые, * поэтому лечение простое — удалить папку data. */ private async assertSchemaIsCurrent() { const dir = getMigrationsDir(); if (!existsSync(dir)) return; // миграции не приехали в поставку — проверять нечего const shipped = readdirSync(dir, { withFileTypes: true }) .filter((e) => e.isDirectory()) .map((e) => e.name); if (shipped.length === 0) return; const rows = await this.$queryRawUnsafe<{ migration_name: string }[]>( 'SELECT migration_name FROM _prisma_migrations WHERE finished_at IS NOT NULL', ); const applied = new Set(rows.map((r) => r.migration_name)); const missing = shipped.filter((name) => !applied.has(name)); if (missing.length > 0) { throw new Error( `Файл базы данных создан более старой версией игры (не хватает миграций: ${missing.join(', ')}). ` + 'Закройте игру, удалите папку data рядом со start.bat и запустите снова — ' + 'результаты прошлых игр не сохраняются, терять нечего.', ); } this.logger.log(`Схема базы актуальна (миграций применено: ${applied.size})`); } }