A House Divided A House DividedDocumentation
Changelog
Engineering/Conventions

Shared utilities and helpers

Last updated 2026-08-21
Source files

This document explains where shared code belongs in A House Divided, when to add a helper versus keeping logic inline, and which existing abstractions to prefer. It complements architecture-boundaries.md and repo-operating-map.md.

Layers at a glance#

Area Primary location Notes
HTTP / JSON / query parsing src/lib/api/ (validate.ts, route helpers) Use with API routes and shared Zod schemas.
MongoDB ObjectId strings (client-safe), objectId.ts (parse) Hex regex / branching vs ObjectId construction; see below.
Display formatting (currency, dates, time) UI and API responses that need consistent copy.
User input in Mongo $regex Always escape before interpolating into regex.
Simulation / game rules src/lib/ domain modules (turn/, electionEngine/, etc.) Prefer named domain functions over generic “math helpers.”
DB types and collection accessors src/lib/db/types/, src/lib/db/collections/ Not “utilities”—keep typed data access explicit.

When to add a shared helper#

Add a helper when all of the following are true:

  1. The same behavior appears in multiple places (not “might appear later”).
  2. The behavior is stable—bug fixes should apply everywhere at once.
  3. A single name makes call sites clearer than repeating the implementation.

Examples already in the codebase: parseJsonBody, escapeRegex, formatCurrency, parseObjectId, parseBoundedIntParam for repeated query-limit patterns, isHexObjectIdString for distinguishing ObjectId strings from other route segments.

When to keep logic inline#

Prefer inline code when:

Duplicating a five-line block once is cheaper than maintaining the wrong abstraction.

ObjectId and validation#

Avoid sprinkling raw /^[a-f0-9]{24}$/i across the codebase; extend objectIdHex.ts / validate.ts if the rule evolves.

API routes#

Follow AGENTS.md and API Route Checklist: use the appropriate require* guard, parseJsonBody, handleRouteError, and shared schemas under src/lib/api/schemas/.

For numeric query params with defaults and min/max caps, use parseBoundedIntParam so missing keys, NaN, and out-of-range values behave consistently.

Formatting and UI#

Use formatters.ts for money, population, and dates shown to players unless a design explicitly requires a different locale or precision variant (formatCurrencyPrecise, formatCurrencyFull).

Do not introduce a generic “string format” layer for one-off labels.

Tests#

New shared helpers should have co-located Vitest coverage (*.test.ts next to the module or under src/lib/api/ for validate.ts).

Deferred: unifying every API route’s limit/page parsing (different defaults and caps per route); deduplicating local isSeatId implementations beyond the shared hex check—the remaining seat-slug rules stay in each route until a single well-named election-route helper is justified by more call sites.