fix(server): lazy-load game validators to avoid ES module errors
Problem: - Production server failing at startup with "Cannot find name 'scaleX'" and ES module errors - @svg-maps/world ES module being imported in CommonJS context - server.js uses require() which can't handle ES modules Root cause: - validators.ts imported all validators at module load time - know-your-world validator imports maps.ts - maps.ts imports @svg-maps/world (ES module) - When server.js requires validators.ts, it fails Solution: - Convert validators.ts to use lazy loading with dynamic imports - Validators are now loaded on-demand when first requested - Cached after first load for performance - This avoids importing ES modules until actually needed Changes: - validators.ts: Replace static imports with lazy loaders - validators.ts: Make getValidator() async, returns Promise<GameValidator> - session-manager.ts: Add await to getValidator() calls - socket-server.ts: Add await to getValidator() calls - validation/index.ts: Remove re-exports of validator instances - game-registry.ts: Remove validator comparison (can't sync compare async) Impact: - Server.js can now start without ES module errors - Next.js build still works (handles ES modules natively) - Small performance hit on first validator access (cached thereafter) - Breaking change: getValidator() is now async 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
553a3ae7a2
commit
a88bd5844c
|
|
@ -52,11 +52,18 @@
|
|||
"Bash(npm install:*)",
|
||||
"Bash(pnpm add:*)",
|
||||
"Bash(node -e:*)",
|
||||
"Bash(npm search:*)"
|
||||
"Bash(npm search:*)",
|
||||
"Bash(git revert:*)",
|
||||
"Bash(pnpm remove:*)",
|
||||
"Bash(gh run view:*)",
|
||||
"Bash(pnpm install:*)",
|
||||
"Bash(git checkout:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"enabledMcpjsonServers": ["sqlite"]
|
||||
"enabledMcpjsonServers": [
|
||||
"sqlite"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ export async function applyGameMove(
|
|||
}
|
||||
|
||||
// Get the validator for this game
|
||||
const validator = getValidator(session.currentGame as GameName)
|
||||
const validator = await getValidator(session.currentGame as GameName)
|
||||
|
||||
// Fetch player ownership for authorization checks (room-based games)
|
||||
let playerOwnership: PlayerOwnershipMap | undefined
|
||||
|
|
|
|||
|
|
@ -4,16 +4,8 @@
|
|||
* New code should import from '@/lib/arcade/validators' instead
|
||||
*/
|
||||
|
||||
// Re-export everything from unified registry
|
||||
export {
|
||||
getValidator,
|
||||
hasValidator,
|
||||
getRegisteredGameNames,
|
||||
validatorRegistry,
|
||||
matchingGameValidator,
|
||||
memoryQuizGameValidator,
|
||||
cardSortingValidator,
|
||||
} from '../validators'
|
||||
// Re-export core functions and types from unified registry
|
||||
export { getValidator, hasValidator, getRegisteredGameNames } from '../validators'
|
||||
|
||||
export type { GameName } from '../validators'
|
||||
export * from './types'
|
||||
|
|
|
|||
|
|
@ -4,55 +4,70 @@
|
|||
* This is the single source of truth for game validators.
|
||||
* Both client and server import validators from here.
|
||||
*
|
||||
* IMPORTANT: Uses lazy loading to avoid importing ES modules at module load time.
|
||||
* This allows the registry to be loaded on the server without causing ES module errors.
|
||||
*
|
||||
* To add a new game:
|
||||
* 1. Import the validator
|
||||
* 2. Add to validatorRegistry Map
|
||||
* 1. Add the lazy loader function
|
||||
* 2. Add to validatorLoaders Map
|
||||
* 3. GameName type will auto-update
|
||||
*/
|
||||
|
||||
import { matchingGameValidator } from '@/arcade-games/matching/Validator'
|
||||
import { memoryQuizGameValidator } from '@/arcade-games/memory-quiz/Validator'
|
||||
import { complementRaceValidator } from '@/arcade-games/complement-race/Validator'
|
||||
import { cardSortingValidator } from '@/arcade-games/card-sorting/Validator'
|
||||
import { yjsDemoValidator } from '@/arcade-games/yjs-demo/Validator'
|
||||
import { rithmomachiaValidator } from '@/arcade-games/rithmomachia/Validator'
|
||||
import { knowYourWorldValidator } from '@/arcade-games/know-your-world/Validator'
|
||||
import type { GameValidator } from './validation/types'
|
||||
|
||||
/**
|
||||
* Central registry of all game validators
|
||||
* Key: game name (matches manifest.name)
|
||||
* Value: validator instance
|
||||
* Lazy validator loaders - import validators only when needed
|
||||
*/
|
||||
export const validatorRegistry = {
|
||||
matching: matchingGameValidator,
|
||||
'memory-quiz': memoryQuizGameValidator,
|
||||
'complement-race': complementRaceValidator,
|
||||
'card-sorting': cardSortingValidator,
|
||||
'yjs-demo': yjsDemoValidator,
|
||||
rithmomachia: rithmomachiaValidator,
|
||||
'know-your-world': knowYourWorldValidator,
|
||||
const validatorLoaders = {
|
||||
matching: async () => (await import('@/arcade-games/matching/Validator')).matchingGameValidator,
|
||||
'memory-quiz': async () =>
|
||||
(await import('@/arcade-games/memory-quiz/Validator')).memoryQuizGameValidator,
|
||||
'complement-race': async () =>
|
||||
(await import('@/arcade-games/complement-race/Validator')).complementRaceValidator,
|
||||
'card-sorting': async () =>
|
||||
(await import('@/arcade-games/card-sorting/Validator')).cardSortingValidator,
|
||||
'yjs-demo': async () => (await import('@/arcade-games/yjs-demo/Validator')).yjsDemoValidator,
|
||||
rithmomachia: async () =>
|
||||
(await import('@/arcade-games/rithmomachia/Validator')).rithmomachiaValidator,
|
||||
'know-your-world': async () =>
|
||||
(await import('@/arcade-games/know-your-world/Validator')).knowYourWorldValidator,
|
||||
// Add new games here - GameName type will auto-update
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Cache for loaded validators
|
||||
*/
|
||||
const validatorCache = new Map<GameName, GameValidator>()
|
||||
|
||||
/**
|
||||
* Auto-derived game name type from registry
|
||||
* No need to manually update this!
|
||||
*/
|
||||
export type GameName = keyof typeof validatorRegistry
|
||||
export type GameName = keyof typeof validatorLoaders
|
||||
|
||||
/**
|
||||
* Get validator for a game
|
||||
* Get validator for a game (async - lazy loads validator)
|
||||
* @throws Error if game not found (fail fast)
|
||||
*/
|
||||
export function getValidator(gameName: string): GameValidator {
|
||||
const validator = validatorRegistry[gameName as GameName]
|
||||
if (!validator) {
|
||||
export async function getValidator(gameName: string): Promise<GameValidator> {
|
||||
const gameNameTyped = gameName as GameName
|
||||
|
||||
// Check cache first
|
||||
if (validatorCache.has(gameNameTyped)) {
|
||||
return validatorCache.get(gameNameTyped)!
|
||||
}
|
||||
|
||||
const loader = validatorLoaders[gameNameTyped]
|
||||
if (!loader) {
|
||||
throw new Error(
|
||||
`No validator found for game: ${gameName}. ` +
|
||||
`Available games: ${Object.keys(validatorRegistry).join(', ')}`
|
||||
`Available games: ${Object.keys(validatorLoaders).join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
// Load and cache
|
||||
const validator = await loader()
|
||||
validatorCache.set(gameNameTyped, validator)
|
||||
return validator
|
||||
}
|
||||
|
||||
|
|
@ -60,14 +75,14 @@ export function getValidator(gameName: string): GameValidator {
|
|||
* Check if a game has a registered validator
|
||||
*/
|
||||
export function hasValidator(gameName: string): gameName is GameName {
|
||||
return gameName in validatorRegistry
|
||||
return gameName in validatorLoaders
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered game names
|
||||
*/
|
||||
export function getRegisteredGameNames(): GameName[] {
|
||||
return Object.keys(validatorRegistry) as GameName[]
|
||||
return Object.keys(validatorLoaders) as GameName[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -94,16 +109,3 @@ export function assertValidGameName(gameName: unknown): asserts gameName is Game
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-export validators for backwards compatibility
|
||||
*/
|
||||
export {
|
||||
matchingGameValidator,
|
||||
memoryQuizGameValidator,
|
||||
complementRaceValidator,
|
||||
cardSortingValidator,
|
||||
yjsDemoValidator,
|
||||
rithmomachiaValidator,
|
||||
knowYourWorldValidator,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ export function initializeSocketServer(httpServer: HTTPServer) {
|
|||
const roomPlayerIds = await getRoomPlayerIds(roomId)
|
||||
|
||||
// Get initial state from the correct validator based on game type
|
||||
const validator = getValidator(room.gameName as GameName)
|
||||
const validator = await getValidator(room.gameName as GameName)
|
||||
|
||||
// Get game-specific config from database (type-safe)
|
||||
const gameConfig = await getGameConfig(roomId, room.gameName as GameName)
|
||||
|
|
@ -418,7 +418,7 @@ export function initializeSocketServer(httpServer: HTTPServer) {
|
|||
}
|
||||
|
||||
// Get initial state from validator (this code path is matching-game specific)
|
||||
const matchingValidator = getValidator('matching')
|
||||
const matchingValidator = await getValidator('matching')
|
||||
const initialState = matchingValidator.getInitialState({
|
||||
difficulty: 6,
|
||||
gameType: 'abacus-numeral',
|
||||
|
|
|
|||
Loading…
Reference in New Issue