feat: refactor utility functions for improved consistency and usability; consolidate parsing logic

This commit is contained in:
2026-01-17 12:36:58 +00:00
parent bc76b6e725
commit 07ea17dacf
25 changed files with 162 additions and 211 deletions
+1 -7
View File
@@ -2,6 +2,7 @@ import type { WorldStateRow } from '../context.js';
import type { BattleSimJobPayload, BattleSimRequestPayload } from './types.js';
import { loadUnitSetDefinitionByName } from './unitSetLoader.js';
import type { WarEngineConfig } from '@sammo-ts/logic';
import { asRecord } from '@sammo-ts/common';
const DEFAULT_WAR_CONFIG = {
armPerPhase: 500,
@@ -11,13 +12,6 @@ const DEFAULT_WAR_CONFIG = {
maxAtmosByWar: 150,
};
const asRecord = (value: unknown): Record<string, unknown> => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {};
}
return value as Record<string, unknown>;
};
const resolveNumber = (record: Record<string, unknown>, keys: string[], fallback: number): number => {
for (const key of keys) {
const value = record[key];
+10 -15
View File
@@ -1,3 +1,5 @@
import { parseNumberWithFallback } from '@sammo-ts/common';
export interface GameApiConfig {
host: string;
port: number;
@@ -13,17 +15,6 @@ export interface GameApiConfig {
flushChannel: string;
}
const parseNumber = (value: string | undefined, fallback: number, label: string): number => {
if (!value) {
return fallback;
}
const parsed = Number(value);
if (Number.isNaN(parsed)) {
throw new Error(`${label} must be a number.`);
}
return parsed;
};
export const resolveGameApiConfigFromEnv = (env: NodeJS.ProcessEnv = process.env): GameApiConfig => {
const profile = env.PROFILE ?? env.SERVER_PROFILE ?? 'hwe';
const scenario = env.SCENARIO ?? 'default';
@@ -36,19 +27,23 @@ export const resolveGameApiConfigFromEnv = (env: NodeJS.ProcessEnv = process.env
return {
host: env.GAME_API_HOST ?? '0.0.0.0',
port: parseNumber(env.GAME_API_PORT, 14000, 'GAME_API_PORT'),
port: parseNumberWithFallback(env.GAME_API_PORT, 14000, 'GAME_API_PORT'),
trpcPath: env.TRPC_PATH ?? '/trpc',
eventsPath: env.GAME_API_EVENTS_PATH ?? '/events',
profile,
scenario,
profileName,
daemonRequestTimeoutMs: parseNumber(env.DAEMON_REQUEST_TIMEOUT_MS, 5000, 'DAEMON_REQUEST_TIMEOUT_MS'),
battleSimRequestTimeoutMs: parseNumber(
daemonRequestTimeoutMs: parseNumberWithFallback(env.DAEMON_REQUEST_TIMEOUT_MS, 5000, 'DAEMON_REQUEST_TIMEOUT_MS'),
battleSimRequestTimeoutMs: parseNumberWithFallback(
env.BATTLE_SIM_REQUEST_TIMEOUT_MS,
8000,
'BATTLE_SIM_REQUEST_TIMEOUT_MS'
),
battleSimResultTtlSeconds: parseNumber(env.BATTLE_SIM_RESULT_TTL_SECONDS, 60, 'BATTLE_SIM_RESULT_TTL_SECONDS'),
battleSimResultTtlSeconds: parseNumberWithFallback(
env.BATTLE_SIM_RESULT_TTL_SECONDS,
60,
'BATTLE_SIM_RESULT_TTL_SECONDS'
),
gameTokenSecret: secret,
flushChannel: `${gatewayPrefix}:flush`,
};
+1 -5
View File
@@ -1,4 +1,5 @@
import type { GameApiContext, WorldStateRow } from '../context.js';
import { asRecord, isRecord } from '@sammo-ts/common';
export type MapCityCompact = [number, number, number, number, number, number];
export type MapNationCompact = [number, string, string, number];
@@ -45,11 +46,6 @@ const MAP_VERSION = 0 as const;
const BASE_MAP_TTL_SECONDS = 30;
const PUBLIC_MAP_TTL_SECONDS = 600;
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === 'object' && !Array.isArray(value);
const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const resolveStartYear = (worldState: WorldStateRow): number => {
const meta = asRecord(worldState.meta);
const scenarioMeta = asRecord(meta.scenarioMeta);
+1 -9
View File
@@ -4,6 +4,7 @@ import { randomBytes } from 'node:crypto';
import type { WorldStateRow } from '../../context.js';
import { authedProcedure, router } from '../../trpc.js';
import { asNumber, asRecord, asStringArray } from '@sammo-ts/common';
import {
isPersonalityTraitKey,
isWarTraitKey,
@@ -23,15 +24,6 @@ const DEFAULT_JOIN_STAT = {
bonusMax: 5,
};
const asRecord = (value: unknown): Record<string, unknown> =>
value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
const asNumber = (value: unknown, fallback: number): number =>
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
const asStringArray = (value: unknown): string[] =>
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
const resolveJoinStat = (worldState: WorldStateRow) => {
const config = asRecord(worldState.config);
const stat = asRecord(config.stat);
+1 -5
View File
@@ -13,6 +13,7 @@ import type {
TriggerValue,
} from '@sammo-ts/logic';
import { evaluateConstraints, loadGeneralTurnCommandSpecs, loadNationTurnCommandSpecs } from '@sammo-ts/logic';
import { asRecord, isRecord } from '@sammo-ts/common';
import type { CityRow, GeneralRow, NationRow, WorldStateRow } from '../context.js';
import { loadTurnCommandProfile } from './turnCommandProfile.js';
@@ -73,11 +74,6 @@ const DEFAULT_GENERAL_GOLD = 1000;
const DEFAULT_GENERAL_RICE = 1000;
const DEFAULT_CREW_TYPE_ID = 1100;
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === 'object' && !Array.isArray(value);
const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const asTriggerRecord = (value: unknown): Record<string, TriggerValue> =>
isRecord(value) ? (value as Record<string, TriggerValue>) : {};
+1 -3
View File
@@ -1,4 +1,5 @@
import type { DatabaseClient, GeneralTurnRow, NationTurnRow, InputJsonValue } from '../context.js';
import { isRecord } from '@sammo-ts/common';
export const DEFAULT_TURN_ACTION = '휴식';
export const MAX_GENERAL_TURNS = 30;
@@ -15,9 +16,6 @@ export interface ReservedTurnView {
args: InputJsonValue;
}
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === 'object' && !Array.isArray(value);
const normalizeAction = (action: string | null | undefined): string =>
action && action.length > 0 ? action : DEFAULT_TURN_ACTION;