feat: 계정 Web Push 전달 기반과 사건 판정을 추가한다

게임 상태 변경과 같은 트랜잭션에 내구성 outbox를 기록하고 Gateway가 계정 설정과 기기 구독으로 fan-out하도록 한다. 전역 기본값은 비활성으로 유지하며 migration과 회귀 테스트를 함께 추가한다.
This commit is contained in:
2026-08-23 13:16:07 +00:00
parent f858806295
commit 7bc3213b03
34 changed files with 1964 additions and 9 deletions
+14
View File
@@ -3,6 +3,8 @@ import {
createGamePostgresConnector,
GamePrisma,
writeReadModelChangeJournal,
enqueuePrivateMessageWebPush,
enqueueWebPushOutboxEvents,
type InputJsonValue,
type ReadModelJournalWriteResult,
type TurnEngineCityUpdateInput,
@@ -50,6 +52,7 @@ import { buildPersistedRankRows } from './rankData.js';
import { persistUnificationFinalization } from './unificationPersistence.js';
import { buildOldNationArchiveData } from './oldNationArchive.js';
import { persistYearbookSnapshot } from './yearbookPersistence.js';
import { buildTurnWebPushEvents, captureWebPushTurnBaseline } from './webPushEvents.js';
export interface DatabaseTurnHooks {
hooks: TurnDaemonHooks;
@@ -1039,6 +1042,7 @@ export const createDatabaseTurnHooks = async (
const readModelBaseline = createRealtimeReadModelBaseline(world);
let worldReadModelBaseline = createWorldReadModelSignature(world);
let persistedTickSeconds = world.getState().tickSeconds;
let webPushTurnBaseline = captureWebPushTurnBaseline(world, options?.reservedTurns);
const committedReceipts = new Map<bigint, CommittedReadModelChangeReceipt>();
const enqueueCommittedReceipt = (
@@ -1109,6 +1113,13 @@ export const createDatabaseTurnHooks = async (
const persistedReservedTurnChanges = reservedTurnChanges
? excludeDeletedReservedTurnQueues(reservedTurnChanges, deletedGenerals, deletedNations)
: undefined;
const nextWebPushTurnBaseline = captureWebPushTurnBaseline(world, options?.reservedTurns);
const webPushEvents = buildTurnWebPushEvents({
before: webPushTurnBaseline,
after: nextWebPushTurnBaseline,
changes,
...(persistedReservedTurnChanges ? { reservedTurnChanges: persistedReservedTurnChanges } : {}),
});
const worldStateUpdate: TurnEngineWorldStateUpdateInput = {
currentYear: state.currentYear,
@@ -1597,6 +1608,7 @@ export const createDatabaseTurnHooks = async (
if (!id) {
throw new Error('Failed to persist turn message.');
}
await enqueuePrivateMessageWebPush(prisma, draft, id);
persistedMessageMailboxes.push(draft.mailbox);
return id;
},
@@ -1657,6 +1669,7 @@ export const createDatabaseTurnHooks = async (
journal.mark('betting');
}
const journalWrite = await writeReadModelChangeJournal(prisma, journal.snapshot());
await enqueueWebPushOutboxEvents(prisma, webPushEvents);
return { readModelChanges, journalWrite, worldReadModelSignature };
};
const persisted = transaction
@@ -1671,6 +1684,7 @@ export const createDatabaseTurnHooks = async (
applyRealtimeReadModelBaseline(readModelBaseline, changes);
worldReadModelBaseline = persisted.worldReadModelSignature;
persistedTickSeconds = state.tickSeconds;
webPushTurnBaseline = nextWebPushTurnBaseline;
},
readModelChanges: persisted.readModelChanges,
journalWrite: persisted.journalWrite,