갱신 행의 ID를 보존하고 예약턴의 불필요한 시퀀스를 제거한다
This commit is contained in:
1 parent
bc54b157c1
commit
81e101ffef
43 files changed
+587
-113
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { existingRowIdOrNext } from '@sammo-ts/infra';
|
||||
import { resolveMessageTargetIcon } from '@sammo-ts/logic';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
@@ -580,8 +581,8 @@ export const createAuctionBidder = async (options: {
|
||||
}
|
||||
await tx.$executeRaw(
|
||||
GamePrisma.sql`
|
||||
INSERT INTO rank_data (nation_id, general_id, type, value)
|
||||
SELECT nation_id, id, 'inherit_spent_dyn', ${morePoint}
|
||||
INSERT INTO rank_data (id, nation_id, general_id, type, value)
|
||||
SELECT ${existingRowIdOrNext('rank_data', GamePrisma.sql`stable_id.general_id = "general".id AND stable_id.type = 'inherit_spent_dyn'`)}, nation_id, id, 'inherit_spent_dyn', ${morePoint}
|
||||
FROM general
|
||||
WHERE id = ${command.generalId}
|
||||
ON CONFLICT (general_id, type)
|
||||
@@ -598,8 +599,9 @@ export const createAuctionBidder = async (options: {
|
||||
}
|
||||
await tx.$executeRaw(
|
||||
GamePrisma.sql`
|
||||
INSERT INTO inheritance_point (user_id, key, value, updated_at)
|
||||
INSERT INTO inheritance_point (id, user_id, key, value, updated_at)
|
||||
VALUES (
|
||||
${existingRowIdOrNext('inheritance_point', GamePrisma.sql`stable_id.user_id = ${prevUserId} AND stable_id.key = 'previous'`)},
|
||||
${prevUserId},
|
||||
'previous',
|
||||
${highestBid.amount},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existingRowIdOrNext } from '@sammo-ts/infra';
|
||||
import { resolveMessageTargetIcon } from '@sammo-ts/logic';
|
||||
import { createGamePostgresConnector, GamePrisma } from '@sammo-ts/infra';
|
||||
import { ActionLogger, ItemLoader, LogFormat, isItemKey, type MessageDraft } from '@sammo-ts/logic';
|
||||
@@ -212,8 +213,8 @@ const refundInheritancePoint = async (options: {
|
||||
}
|
||||
await prisma.$executeRaw(
|
||||
GamePrisma.sql`
|
||||
INSERT INTO inheritance_point (user_id, key, value, updated_at)
|
||||
VALUES (${userId}, 'previous', ${amount}, ${new Date()})
|
||||
INSERT INTO inheritance_point (id, user_id, key, value, updated_at)
|
||||
VALUES (${existingRowIdOrNext('inheritance_point', GamePrisma.sql`stable_id.user_id = ${userId} AND stable_id.key = 'previous'`)}, ${userId}, 'previous', ${amount}, ${new Date()})
|
||||
ON CONFLICT (user_id, key)
|
||||
DO UPDATE SET
|
||||
value = inheritance_point.value + EXCLUDED.value,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existingRowIdOrNext } from '@sammo-ts/infra';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { asRecord, JosaUtil } from '@sammo-ts/common';
|
||||
@@ -289,8 +290,8 @@ const openUniqueAuction = async (
|
||||
});
|
||||
await db.$executeRaw(
|
||||
GamePrisma.sql`
|
||||
INSERT INTO rank_data (nation_id, general_id, type, value)
|
||||
VALUES (${general.nationId}, ${general.id}, 'inherit_spent_dyn', ${command.amount})
|
||||
INSERT INTO rank_data (id, nation_id, general_id, type, value)
|
||||
VALUES (${existingRowIdOrNext('rank_data', GamePrisma.sql`stable_id.general_id = ${general.id} AND stable_id.type = 'inherit_spent_dyn'`)}, ${general.nationId}, ${general.id}, 'inherit_spent_dyn', ${command.amount})
|
||||
ON CONFLICT (general_id, type)
|
||||
DO UPDATE SET
|
||||
nation_id = EXCLUDED.nation_id,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
import { createGamePostgresConnector, type GamePrisma, type InputJsonValue } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
@@ -424,7 +425,7 @@ const cancelGameInTransaction = async (
|
||||
earnedPointRetentionPercent: request.earnedPointRetentionPercent,
|
||||
});
|
||||
const currentPoint = pointsByUser.get(userId)?.get('previous') ?? 0;
|
||||
await prisma.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(prisma.inheritancePoint, {
|
||||
where: { userId_key: { userId, key: 'previous' } },
|
||||
update: { value: calculated.finalPoint },
|
||||
create: { userId, key: 'previous', value: calculated.finalPoint },
|
||||
@@ -470,7 +471,7 @@ const cancelGameInTransaction = async (
|
||||
}
|
||||
for (const general of activeGenerals) {
|
||||
if (!general.userId || general.npcState >= 2) continue;
|
||||
await prisma.oldGeneral.upsert({
|
||||
await upsertWithoutSequence(prisma.oldGeneral, {
|
||||
where: { by_no: { serverId, generalNo: general.id } },
|
||||
update: {
|
||||
owner: general.userId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { prunePreviousAuditBatch } from '../playAudit/retention.js';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
@@ -497,7 +498,7 @@ export const seedScenarioToDatabase = async (options: ScenarioSeedOptions): Prom
|
||||
);
|
||||
}
|
||||
}
|
||||
await prisma.gameHistory.upsert({
|
||||
await upsertWithoutSequence(prisma.gameHistory, {
|
||||
where: { serverId: worldMeta.serverId },
|
||||
create: {
|
||||
serverId: worldMeta.serverId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { areSeasonRecordsFinalized } from '../turn/seasonRecords.js';
|
||||
import { JosaUtil, asRecord } from '@sammo-ts/common';
|
||||
import { createGamePostgresConnector, type GamePrisma } from '@sammo-ts/infra';
|
||||
@@ -256,7 +257,7 @@ export const createTournamentRewardFinalizer = async (options: {
|
||||
|
||||
const recordsFinalized = await areSeasonRecordsFinalized(db, world.getState().meta.serverId);
|
||||
for (const entry of recordsFinalized ? [] : pointUpdates) {
|
||||
await db.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(db.inheritancePoint, {
|
||||
where: {
|
||||
userId_key: { userId: entry.userId!, key: 'tournament' },
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { persistAuditDecisions } from '../playAudit/decisionPersistence.js';
|
||||
import { collectPlayAudit, markAuditGap } from '../playAudit/bestEffort.js';
|
||||
import { persistAuditDiplomacyEvents, withPlayAuditSavepoint } from '@sammo-ts/infra';
|
||||
@@ -697,12 +698,12 @@ const persistNationBettingFinish = async (
|
||||
});
|
||||
const previousPoint = existing?.value ?? 0;
|
||||
const nextPoint = previousPoint + reward.amount;
|
||||
await prisma.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(prisma.inheritancePoint, {
|
||||
where: { userId_key: { userId: reward.userId, key: 'previous' } },
|
||||
update: { value: nextPoint },
|
||||
create: { userId: reward.userId, key: 'previous', value: nextPoint },
|
||||
});
|
||||
await prisma.rankData.upsert({
|
||||
await upsertWithoutSequence(prisma.rankData, {
|
||||
where: {
|
||||
generalId_type: {
|
||||
generalId: reward.generalId,
|
||||
@@ -1574,7 +1575,7 @@ export const createDatabaseTurnHooks = async (
|
||||
}
|
||||
}
|
||||
for (const entry of grouped.values()) {
|
||||
await prisma.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(prisma.inheritancePoint, {
|
||||
where: { userId_key: { userId: entry.userId, key: entry.key } },
|
||||
update: { value: { increment: entry.amount } },
|
||||
create: { userId: entry.userId, key: entry.key, value: entry.amount },
|
||||
@@ -1642,7 +1643,7 @@ export const createDatabaseTurnHooks = async (
|
||||
}
|
||||
await Promise.all(
|
||||
deletedNationSnapshots.map((snapshot) =>
|
||||
prisma.oldNation.upsert({
|
||||
upsertWithoutSequence(prisma.oldNation, {
|
||||
where: {
|
||||
serverId_nation_sourceId: {
|
||||
serverId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existingRowIdOrNext } from '@sammo-ts/infra';
|
||||
import { GamePrisma, type TurnEngineGeneralUpdateInput } from '@sammo-ts/infra';
|
||||
|
||||
// Fixed schema identifiers only; all row values remain bound parameters.
|
||||
@@ -91,8 +92,8 @@ export const persistGeneralAccessScores = async (
|
||||
for (let offset = 0; offset < updates.length; offset += GENERAL_UPDATE_BATCH_SIZE) {
|
||||
const batch = updates.slice(offset, offset + GENERAL_UPDATE_BATCH_SIZE);
|
||||
await database.$executeRaw(GamePrisma.sql`
|
||||
INSERT INTO "general_access_log" ("general_id", "user_id", "refresh_score_total")
|
||||
VALUES ${GamePrisma.join(batch.map((row) => GamePrisma.sql`(${row.generalId}, ${row.userId}, ${row.refreshScoreTotal})`))}
|
||||
INSERT INTO "general_access_log" ("id", "general_id", "user_id", "refresh_score_total")
|
||||
VALUES ${GamePrisma.join(batch.map((row) => GamePrisma.sql`(${existingRowIdOrNext('general_access_log', GamePrisma.sql`stable_id.general_id = ${row.generalId}`)}, ${row.generalId}, ${row.userId}, ${row.refreshScoreTotal})`))}
|
||||
ON CONFLICT ("general_id") DO UPDATE
|
||||
SET "refresh_score_total" = EXCLUDED."refresh_score_total"
|
||||
`);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { areSeasonRecordsFinalized } from './seasonRecords.js';
|
||||
import {
|
||||
asRecord,
|
||||
@@ -77,7 +78,7 @@ const persistPostRetirementRankValues = async (
|
||||
return;
|
||||
}
|
||||
for (const row of buildPersistedRankRows(event.after)) {
|
||||
await prisma.rankData.upsert({
|
||||
await upsertWithoutSequence(prisma.rankData, {
|
||||
where: { generalId_type: { generalId: row.generalId, type: row.type } },
|
||||
update: { nationId: row.nationId, value: row.value },
|
||||
create: row,
|
||||
@@ -142,7 +143,7 @@ const settleInheritance = async (
|
||||
);
|
||||
const total = Math.trunc(previous + refund + settlement.totalEarned);
|
||||
|
||||
await prisma.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(prisma.inheritancePoint, {
|
||||
where: { userId_key: { userId, key: 'previous' } },
|
||||
update: { value: total },
|
||||
create: { userId, key: 'previous', value: total },
|
||||
@@ -152,7 +153,7 @@ const settleInheritance = async (
|
||||
([key, value]) => key === 'max_belong' || points.has(key) || value !== 0
|
||||
);
|
||||
for (const [key, value] of retainedEntries) {
|
||||
await prisma.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(prisma.inheritancePoint, {
|
||||
where: { userId_key: { userId, key } },
|
||||
update: { value },
|
||||
create: { userId, key, value },
|
||||
@@ -367,7 +368,7 @@ const archiveGeneral = async (
|
||||
records: { battleResult: battleResults },
|
||||
availability: { battleResultLogs: true },
|
||||
};
|
||||
await prisma.oldGeneral.upsert({
|
||||
await upsertWithoutSequence(prisma.oldGeneral, {
|
||||
where: { by_no: { serverId, generalNo: event.generalId } },
|
||||
update: {
|
||||
owner: event.before.userId ?? null,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { areSeasonRecordsFinalized } from './seasonRecords.js';
|
||||
import { asNumber, asRecord, JosaUtil, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
|
||||
import { acquireGameSchemaAdvisoryXactLock, GamePrisma } from '@sammo-ts/infra';
|
||||
@@ -246,7 +247,7 @@ const applyInheritanceUser = async (
|
||||
};
|
||||
|
||||
const setInheritancePoint = async (db: DatabaseClient, userId: string, value: number): Promise<void> => {
|
||||
await db.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(db.inheritancePoint, {
|
||||
where: { userId_key: { userId, key: 'previous' } },
|
||||
update: { value },
|
||||
create: { userId, key: 'previous', value },
|
||||
@@ -860,7 +861,7 @@ export const createGeneralFromJoin = async (options: {
|
||||
await db.generalTurnRevision.create({
|
||||
data: { generalId, revision: 0 },
|
||||
});
|
||||
await db.generalAccessLog.upsert({
|
||||
await upsertWithoutSequence(db.generalAccessLog, {
|
||||
where: { generalId },
|
||||
update: { userId: input.userId, lastRefresh: operationalAcceptedAt },
|
||||
create: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { randomInt } from 'node:crypto';
|
||||
|
||||
import { asNumber, asRecord, GAME_TICKS_PER_TURN, JosaUtil, LiteHashDRBG, RandUtil, type RNG } from '@sammo-ts/common';
|
||||
@@ -337,18 +338,12 @@ export const reserveNpcPossessionCandidates = async (options: {
|
||||
}
|
||||
return toReservation(inFlightToken, options.createdGameTick, ticksPerSecond);
|
||||
}
|
||||
if (
|
||||
existing &&
|
||||
(existing.validUntilTick === null || Number(existing.validUntilTick) < options.createdGameTick)
|
||||
) {
|
||||
if (existing && (existing.validUntilTick === null || Number(existing.validUntilTick) < options.createdGameTick)) {
|
||||
await db.npcSelectionToken.deleteMany({
|
||||
where: {
|
||||
ownerUserId: userId,
|
||||
nonce: existing.nonce,
|
||||
OR: [
|
||||
{ validUntilTick: null },
|
||||
{ validUntilTick: { lt: BigInt(options.createdGameTick) } },
|
||||
],
|
||||
OR: [{ validUntilTick: null }, { validUntilTick: { lt: BigInt(options.createdGameTick) } }],
|
||||
},
|
||||
});
|
||||
existing = null;
|
||||
@@ -425,9 +420,7 @@ export const reserveNpcPossessionCandidates = async (options: {
|
||||
const validUntilTick = options.createdGameTick + Math.round(validSeconds * ticksPerSecond);
|
||||
const pickMoreFromTick = options.createdGameTick + Math.round(pickMoreSeconds * ticksPerSecond);
|
||||
const validUntil = new Date(now.getTime() + validSeconds * 1000);
|
||||
const refreshedPickMoreFrom = new Date(
|
||||
now.getTime() + pickMoreSeconds * 1000
|
||||
);
|
||||
const refreshedPickMoreFrom = new Date(now.getTime() + pickMoreSeconds * 1000);
|
||||
const nonce = randomInt(0, 0x10000000);
|
||||
|
||||
if (existing) {
|
||||
@@ -566,7 +559,7 @@ export const possessNpcGeneral = async (options: {
|
||||
},
|
||||
});
|
||||
|
||||
await db.generalAccessLog.upsert({
|
||||
await upsertWithoutSequence(db.generalAccessLog, {
|
||||
where: { generalId },
|
||||
update: {
|
||||
userId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { asNumber, asRecord, GAME_TICKS_PER_TURN, JosaUtil, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
|
||||
@@ -567,11 +568,7 @@ const assertGeneralIdSnapshotMatches = async (db: DatabaseClient, world: InMemor
|
||||
}
|
||||
};
|
||||
|
||||
const clearUnusedReservations = async (
|
||||
db: DatabaseClient,
|
||||
userId: string,
|
||||
nowTick: number
|
||||
): Promise<void> => {
|
||||
const clearUnusedReservations = async (db: DatabaseClient, userId: string, nowTick: number): Promise<void> => {
|
||||
await db.selectPoolEntry.updateMany({
|
||||
where: {
|
||||
generalId: null,
|
||||
@@ -914,7 +911,7 @@ export const createGeneralFromSelectionPool = async (options: {
|
||||
if (occupied.count === 0) {
|
||||
throw new Error('장수 등록 중 선택 후보 점유에 실패했습니다.');
|
||||
}
|
||||
await db.generalAccessLog.upsert({
|
||||
await upsertWithoutSequence(db.generalAccessLog, {
|
||||
where: { generalId },
|
||||
update: { userId, lastRefresh: options.operationalAcceptedAt },
|
||||
create: { generalId, userId, lastRefresh: options.operationalAcceptedAt },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { resolveMessageTargetIcon } from '@sammo-ts/logic';
|
||||
import { areSeasonRecordsFinalized } from './seasonRecords.js';
|
||||
import { asRecord, HALL_OF_FAME_TYPES, resolveLegacyTextColor, type HallOfFameType } from '@sammo-ts/common';
|
||||
@@ -209,12 +210,12 @@ const cancelPendingUniqueAuctions = async (
|
||||
if (!bidder?.userId || bidder.userId !== dbBidder?.userId) {
|
||||
throw new Error(`Unification auction refund owner is unavailable: ${planned.auctionId}.`);
|
||||
}
|
||||
await transaction.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(transaction.inheritancePoint, {
|
||||
where: { userId_key: { userId: bidder.userId, key: 'previous' } },
|
||||
update: { value: { increment: highest.amount } },
|
||||
create: { userId: bidder.userId, key: 'previous', value: highest.amount },
|
||||
});
|
||||
await transaction.rankData.upsert({
|
||||
await upsertWithoutSequence(transaction.rankData, {
|
||||
where: {
|
||||
generalId_type: { generalId: bidder.id, type: 'inherit_spent_dyn' },
|
||||
},
|
||||
@@ -345,7 +346,7 @@ export const persistUnificationFinalization = async (
|
||||
) as Record<(typeof ALL_MERGED_INHERITANCE_KEYS)[number], number>;
|
||||
const total = Math.floor(previous + Object.values(mergedPoints).reduce((sum, value) => sum + value, 0));
|
||||
|
||||
await transaction.inheritancePoint.upsert({
|
||||
await upsertWithoutSequence(transaction.inheritancePoint, {
|
||||
where: { userId_key: { userId, key: 'previous' } },
|
||||
update: { value: total },
|
||||
create: { userId, key: 'previous', value: total },
|
||||
@@ -508,7 +509,7 @@ export const persistUnificationFinalization = async (
|
||||
}),
|
||||
generationKey: input.generationKey,
|
||||
};
|
||||
await transaction.oldNation.upsert({
|
||||
await upsertWithoutSequence(transaction.oldNation, {
|
||||
where: { serverId_nation_sourceId: { serverId, nation: input.winnerNationId, sourceId: 0 } },
|
||||
update: { data: asJson(winnerData), date: input.completedAt },
|
||||
create: {
|
||||
@@ -525,7 +526,7 @@ export const persistUnificationFinalization = async (
|
||||
generals: neutralGenerals.map((general) => general.id),
|
||||
generationKey: input.generationKey,
|
||||
};
|
||||
await transaction.oldNation.upsert({
|
||||
await upsertWithoutSequence(transaction.oldNation, {
|
||||
where: { serverId_nation_sourceId: { serverId, nation: 0, sourceId: 0 } },
|
||||
update: { data: neutralData, date: input.completedAt },
|
||||
create: { serverId, nation: 0, sourceId: 0, data: neutralData, date: input.completedAt },
|
||||
@@ -572,7 +573,7 @@ export const persistUnificationFinalization = async (
|
||||
availability: { battleResultLogs: true },
|
||||
generationKey: input.generationKey,
|
||||
};
|
||||
await transaction.oldGeneral.upsert({
|
||||
await upsertWithoutSequence(transaction.oldGeneral, {
|
||||
where: { by_no: { serverId, generalNo: general.id } },
|
||||
update: {
|
||||
owner: general.userId ?? null,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existingRowIdOrNext } from '@sammo-ts/infra';
|
||||
import { handleCityOfficeRequest, settleCityOfficeRequests } from './cityOfficeRequests.js';
|
||||
import type {
|
||||
TurnDaemonCommandHandler,
|
||||
@@ -2864,8 +2865,8 @@ const insertVoteSelection = async (
|
||||
}
|
||||
const createdAt = new Date();
|
||||
const rows = await ctx.commandDb.$queryRaw<Array<{ id: number }>>(GamePrisma.sql`
|
||||
INSERT INTO vote (vote_id, general_id, nation_id, selection, created_at)
|
||||
SELECT poll.id,
|
||||
INSERT INTO vote (id, vote_id, general_id, nation_id, selection, created_at)
|
||||
SELECT ${existingRowIdOrNext('vote', GamePrisma.sql`stable_id.vote_id = poll.id AND stable_id.general_id = ${general.id}`)}, poll.id,
|
||||
${general.id},
|
||||
${general.nationId},
|
||||
CAST(${JSON.stringify(selection)} AS jsonb),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { upsertWithoutSequence } from '@sammo-ts/infra';
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import type { GamePrisma, InputJsonValue } from '@sammo-ts/infra';
|
||||
@@ -44,7 +45,7 @@ export const persistYearbookSnapshot = async (
|
||||
globalAction,
|
||||
});
|
||||
|
||||
await transaction.yearbookHistory.upsert({
|
||||
await upsertWithoutSequence(transaction.yearbookHistory, {
|
||||
where: {
|
||||
profileName_year_month_sourceId: {
|
||||
profileName: snapshot.serverId,
|
||||
|
||||
@@ -198,6 +198,23 @@ integration('general batch persistence', () => {
|
||||
);
|
||||
});
|
||||
expect(writes).toBe(2);
|
||||
const originalIds = (
|
||||
await db.generalAccessLog.findMany({
|
||||
where: { generalId: { gte: firstId, lte: firstId + GENERAL_UPDATE_BATCH_SIZE } },
|
||||
orderBy: { generalId: 'asc' },
|
||||
})
|
||||
).map((row) => row.id);
|
||||
const sequence = await db.$queryRaw`SELECT last_value FROM general_access_log_id_seq`;
|
||||
await db.$transaction((tx) => persistGeneralAccessScores(tx, updates));
|
||||
expect(
|
||||
(
|
||||
await db.generalAccessLog.findMany({
|
||||
where: { generalId: { gte: firstId, lte: firstId + GENERAL_UPDATE_BATCH_SIZE } },
|
||||
orderBy: { generalId: 'asc' },
|
||||
})
|
||||
).map((row) => row.id)
|
||||
).toEqual(originalIds);
|
||||
expect(await db.$queryRaw`SELECT last_value FROM general_access_log_id_seq`).toEqual(sequence);
|
||||
expect(await db.generalAccessLog.findUniqueOrThrow({ where: { generalId: firstId } })).toMatchObject({
|
||||
userId: 'original-owner',
|
||||
lastRefresh: at,
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { GamePrisma } from '@sammo-ts/infra';
|
||||
import { GamePrisma } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
|
||||
import { persistGeneralLifecycleEvents } from '../src/turn/generalTurnLifecyclePersistence.js';
|
||||
import type { GeneralLifecycleEvent } from '../src/turn/inMemoryWorld.js';
|
||||
import type { TurnGeneral } from '../src/turn/types.js';
|
||||
|
||||
// This harness begins without the durable archive/point row.
|
||||
const missingRecordUpdate = async (): Promise<never> => {
|
||||
throw new GamePrisma.PrismaClientKnownRequestError('fixture record missing', {
|
||||
code: 'P2025',
|
||||
clientVersion: 'fixture',
|
||||
});
|
||||
};
|
||||
|
||||
const archivedGeneral = (): TurnGeneral => ({
|
||||
id: 91,
|
||||
userId: null,
|
||||
@@ -65,12 +73,13 @@ describe('general lifecycle archive history', () => {
|
||||
]),
|
||||
},
|
||||
rankData: {
|
||||
update: missingRecordUpdate,
|
||||
findMany: vi.fn(async () => [
|
||||
{ type: 'warnum', value: 2 },
|
||||
{ type: 'killnum', value: 1 },
|
||||
]),
|
||||
},
|
||||
oldGeneral: { upsert },
|
||||
oldGeneral: { update: missingRecordUpdate, upsert },
|
||||
} as unknown as GamePrisma.TransactionClient;
|
||||
const event: GeneralLifecycleEvent = {
|
||||
generalId: general.id,
|
||||
@@ -137,6 +146,7 @@ describe('general lifecycle archive history', () => {
|
||||
updateMany: vi.fn(async () => ({ count: 1 })),
|
||||
},
|
||||
rankData: {
|
||||
update: missingRecordUpdate,
|
||||
findMany: vi.fn(async () => [
|
||||
{ type: 'warnum', value: 10 },
|
||||
{ type: 'inherit_earned', value: 123 },
|
||||
@@ -156,6 +166,7 @@ describe('general lifecycle archive history', () => {
|
||||
update: vi.fn(async () => undefined),
|
||||
},
|
||||
inheritancePoint: {
|
||||
update: missingRecordUpdate,
|
||||
findMany: vi.fn(async () => [{ key: 'previous', value: 0 }]),
|
||||
upsert: vi.fn(async () => undefined),
|
||||
deleteMany: vi.fn(async () => ({ count: 0 })),
|
||||
|
||||
@@ -68,8 +68,6 @@ describe.skipIf(!databaseUrl)('rank data bigint PostgreSQL persistence', () => {
|
||||
|
||||
it('reloads bigint IDs for high churn queues, logs, upserts and the traffic foreign key', async () => {
|
||||
const tables = [
|
||||
'general_turn',
|
||||
'nation_turn',
|
||||
'general_access_log',
|
||||
'diplomacy',
|
||||
'inheritance_point',
|
||||
@@ -93,10 +91,6 @@ describe.skipIf(!databaseUrl)('rank data bigint PostgreSQL persistence', () => {
|
||||
},
|
||||
});
|
||||
const rows = await Promise.all([
|
||||
tx.generalTurn.create({ data: { generalId, turnIdx: 0, actionCode: 'fixture' } }),
|
||||
tx.nationTurn.create({
|
||||
data: { nationId: 3, officerLevel: 12, turnIdx: 0, actionCode: 'fixture' },
|
||||
}),
|
||||
tx.generalAccessLog.create({ data: { generalId } }),
|
||||
tx.diplomacy.create({ data: { srcNationId: 3, destNationId: 4, stateCode: 0 } }),
|
||||
tx.inheritancePoint.create({ data: { userId: 'bigint-fixture', key: 'previous', value: 123 } }),
|
||||
@@ -109,6 +103,14 @@ describe.skipIf(!databaseUrl)('rank data bigint PostgreSQL persistence', () => {
|
||||
}),
|
||||
]);
|
||||
expect(rows.every((row) => row.id > 2_147_483_647n)).toBe(true);
|
||||
const generalQueue = await tx.generalTurn.create({
|
||||
data: { generalId, turnIdx: 0, actionCode: 'fixture' },
|
||||
});
|
||||
const nationQueue = await tx.nationTurn.create({
|
||||
data: { nationId: 3, officerLevel: 12, turnIdx: 0, actionCode: 'fixture' },
|
||||
});
|
||||
expect(generalQueue).not.toHaveProperty('id');
|
||||
expect(nationQueue).not.toHaveProperty('id');
|
||||
const period = await tx.trafficPeriod.create({
|
||||
data: {
|
||||
worldStateId: world.id,
|
||||
@@ -122,7 +124,7 @@ describe.skipIf(!databaseUrl)('rank data bigint PostgreSQL persistence', () => {
|
||||
});
|
||||
expect(period.id).toBeGreaterThan(2_147_483_647n);
|
||||
expect(period.generals[0]?.periodId).toBe(period.id);
|
||||
expect((await tx.logEntry.findUniqueOrThrow({ where: { id: rows[5]!.id } })).text).toBe('fixture');
|
||||
expect((await tx.logEntry.findUniqueOrThrow({ where: { id: rows[3]!.id } })).text).toBe('fixture');
|
||||
throw new Error('high churn fixture rollback');
|
||||
})
|
||||
).rejects.toThrow('high churn fixture rollback');
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { normalizeArchivedGeneral, type ArchivedJsonValue } from '@sammo-ts/common';
|
||||
import type { GamePrisma } from '@sammo-ts/infra';
|
||||
import { GamePrisma } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope, type City, type Nation } from '@sammo-ts/logic';
|
||||
|
||||
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
|
||||
import { persistUnificationFinalization, resolveStoredInheritancePoint } from '../src/turn/unificationPersistence.js';
|
||||
import type { TurnGeneral, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';
|
||||
|
||||
// This harness begins without the durable archive/point row.
|
||||
const missingRecordUpdate = async (): Promise<never> => {
|
||||
throw new GamePrisma.PrismaClientKnownRequestError('fixture record missing', {
|
||||
code: 'P2025',
|
||||
clientVersion: 'fixture',
|
||||
});
|
||||
};
|
||||
|
||||
const buildWorld = (): InMemoryTurnWorld => {
|
||||
const turnTime = new Date('0190-07-01T00:00:00.000Z');
|
||||
const general: TurnGeneral = {
|
||||
@@ -206,6 +214,7 @@ describe('persistUnificationFinalization', () => {
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
inheritancePoint: {
|
||||
update: missingRecordUpdate,
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{ userId: 'user-1', key: 'previous', value: 100 },
|
||||
{ userId: 'user-1', key: 'unifier', value: 7 },
|
||||
@@ -216,6 +225,7 @@ describe('persistUnificationFinalization', () => {
|
||||
inheritanceResult: { create: inheritanceResultCreate },
|
||||
inheritanceLog: { create: inheritanceLogCreate },
|
||||
rankData: {
|
||||
update: missingRecordUpdate,
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{ generalId: 1, type: 'warnum', value: 15 },
|
||||
{ generalId: 1, type: 'killnum', value: 9 },
|
||||
@@ -251,10 +261,11 @@ describe('persistUnificationFinalization', () => {
|
||||
),
|
||||
},
|
||||
oldNation: {
|
||||
update: missingRecordUpdate,
|
||||
upsert: oldNationUpsert,
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
oldGeneral: { upsert: oldGeneralUpsert },
|
||||
oldGeneral: { update: missingRecordUpdate, upsert: oldGeneralUpsert },
|
||||
emperor: { create: emperorCreate },
|
||||
});
|
||||
await expect(persistUnificationFinalization(transaction, input, buildWorld())).resolves.toEqual({
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { GamePrisma } from '@sammo-ts/infra';
|
||||
import { GamePrisma } from '@sammo-ts/infra';
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
|
||||
import { persistYearbookSnapshot } from '../src/turn/yearbookPersistence.js';
|
||||
|
||||
// This harness begins without the durable archive/point row.
|
||||
const missingRecordUpdate = async (): Promise<never> => {
|
||||
throw new GamePrisma.PrismaClientKnownRequestError('fixture record missing', {
|
||||
code: 'P2025',
|
||||
clientVersion: 'fixture',
|
||||
});
|
||||
};
|
||||
|
||||
describe('persistYearbookSnapshot', () => {
|
||||
it('archives canonical summary logs and compatible action logs together in descending ID order', async () => {
|
||||
const findMany = vi
|
||||
@@ -14,7 +22,7 @@ describe('persistYearbookSnapshot', () => {
|
||||
const upsert = vi.fn().mockResolvedValue(undefined);
|
||||
const transaction = {
|
||||
logEntry: { findMany },
|
||||
yearbookHistory: { upsert },
|
||||
yearbookHistory: { update: missingRecordUpdate, upsert },
|
||||
} as unknown as GamePrisma.TransactionClient;
|
||||
|
||||
await persistYearbookSnapshot(transaction, {
|
||||
|
||||
Reference in new issue
Block a user