Files
initiative/packages/domain/src/add-combatant.ts

51 lines
1.3 KiB
TypeScript

import type { DomainEvent } from "./events.js";
import type { CombatantId, DomainError, Encounter } from "./types.js";
export interface AddCombatantSuccess {
readonly encounter: Encounter;
readonly events: DomainEvent[];
}
/**
* Pure function that adds a combatant to the end of an encounter's list.
*
* FR-001: Accepts an Encounter, CombatantId, and name; returns next state + events.
* FR-002: Appends new combatant to end of combatants list.
* FR-004: Rejects empty/whitespace-only names with DomainError.
* FR-005: Does not alter activeIndex or roundNumber.
* FR-006: Events returned as values, not dispatched via side effects.
*/
export function addCombatant(
encounter: Encounter,
id: CombatantId,
name: string,
): AddCombatantSuccess | DomainError {
const trimmed = name.trim();
if (trimmed === "") {
return {
kind: "domain-error",
code: "invalid-name",
message: "Combatant name must not be empty",
};
}
const position = encounter.combatants.length;
return {
encounter: {
combatants: [...encounter.combatants, { id, name: trimmed }],
activeIndex: encounter.activeIndex,
roundNumber: encounter.roundNumber,
},
events: [
{
type: "CombatantAdded",
combatantId: id,
name: trimmed,
position,
},
],
};
}