Reference
WARCRAFT FOREVER / DEVELOPERS
Type reference
Public SDK contracts generated from the source.
These declarations are generated with each build. See mount options and state and events for usage.
export interface EmbedBuild {
code: string;
revision: string;
path?: string;
}
export type ChromePart = 'title' | 'summary' | 'instructions' | 'reset' | 'share' | 'attribution';
export type EmbedChrome =
| 'bare'
| 'compact'
| 'full'
| ({ preset?: 'bare' | 'compact' | 'full' } & Partial<Record<ChromePart, boolean>>);
export interface EmbedOptions {
class: string;
editable: boolean;
build?: EmbedBuild;
tree?: string | 'all';
maxLevel?: number;
scale?: number;
chrome?: EmbedChrome;
locale?: string;
}
export interface EmbedState {
class: string;
revision: string;
code: string;
allocation: Record<string, number>;
pointsSpent: number;
pointsRemaining: number;
maxLevel: number;
complete: boolean;
shareUrl: string;
}
export interface TalentOptions extends Omit<EmbedOptions, 'editable'> {
view?: 'calculator' | 'tree' | 'icons';
editable?: boolean;
}
export type TalentState = EmbedState;
export interface LegacyOptions extends Omit<TalentOptions, 'class' | 'maxLevel'> {}
export interface LegacyState extends Omit<TalentState, 'class' | 'maxLevel'> {
id: 'legacy';
pointBudget: number;
}
export type TalentEvent = 'ready' | 'change' | 'complete' | 'submit' | 'error';
export interface CalculatorMountConfig extends CalculatorClientConfig {
locales: Record<string, string>;
stylesheet: string;
}
export interface TalentMount {
ready: Promise<TalentState>;
getState(): TalentState | null;
exportBuild(format?: 'blizzard' | 'compact'): string;
exportBuild(format: 'structured'): StructuredTalentBuild;
update(options: Partial<TalentOptions>): Promise<TalentState>;
setBuild(build: EmbedBuild): Promise<TalentState>;
on(event: Exclude<TalentEvent, 'error'>, listener: (state: TalentState) => void): () => void;
on(event: 'error', listener: (error: Error) => void): () => void;
destroy(): void;
}
export interface LegacyMount {
ready: Promise<LegacyState>;
getState(): LegacyState | null;
exportBuild(format?: 'compact'): string;
exportBuild(format: 'structured'): StructuredCalculatorBuild;
update(options: Partial<LegacyOptions>): Promise<LegacyState>;
setBuild(build: EmbedBuild): Promise<LegacyState>;
on(event: Exclude<TalentEvent, 'error'>, listener: (state: LegacyState) => void): () => void;
on(event: 'error', listener: (error: Error) => void): () => void;
destroy(): void;
}
export type ClientLocale = 'enUS' | 'deDE' | 'esES' | 'frFR' | 'koKR' | 'ptBR' | 'ruRU' | 'zhCN';
export type CatalogResource = 'classes' | 'races' | 'factions' | 'pet-families';
export interface CatalogOptions {
/** Immutable catalog dataset from discovery. */
dataset?: string;
channel?: string;
/** Client locale or browser language. Missing client translations use enUS. */
locale?: string;
}
export type JsonReader = (url: URL, options: RequestInit) => Promise<Response>;
export interface CatalogClientOptions extends CatalogOptions {
origin?: string;
reader?: JsonReader;
}
export interface NamedEntity {
id: number;
name: string;
/** Locale of this name, including when it falls back to English. */
nameLocale: ClientLocale;
}
export interface ClassRecord extends NamedEntity {
slug: string;
iconFileDataId: number;
iconUrl?: string;
/** Forever uses one class-wide specialization; this is not a selected talent tree. */
specializations: NamedEntity[];
}
export interface RaceRecord extends NamedEntity {
slug: string;
faction: 'alliance' | 'horde';
classIds: number[];
}
export interface FactionRecord extends NamedEntity {
slug: 'alliance' | 'horde';
}
export interface PetFamilyRecord extends NamedEntity {
iconFileDataId: number | null;
/** Raw client CreatureFamily fields; they do not establish NPC tameability. */
foodMask: number;
talentType: number;
}
export interface CatalogManifest {
schemaVersion: 1;
defaultChannel: string;
resources: CatalogResource[];
channels: Record<
string,
{
dataset: string;
build: string;
product: string;
locales: ClientLocale[];
source: {
product: string;
build: string;
access: string;
tables: Record<string, string>;
archive?: { commit: string; extraction: string };
};
}
>;
}
export interface CatalogEnvelope<T extends NamedEntity> {
schemaVersion: 1;
dataset: string;
channel: string;
build: string;
locale: ClientLocale;
resource: CatalogResource;
data: T[];
}
export interface Collection<T extends NamedEntity> {
list(options?: CatalogOptions): Promise<T[]>;
get(idOrSlug: number | string, options?: CatalogOptions): Promise<T | null>;
}
export interface CatalogSdk {
manifest(): Promise<CatalogManifest>;
Classes: Collection<ClassRecord>;
Races: Collection<RaceRecord>;
Factions: Collection<FactionRecord>;
Pets: {
families(options?: CatalogOptions): Promise<PetFamilyRecord[]>;
family(id: number, options?: CatalogOptions): Promise<PetFamilyRecord | null>;
};
}
export interface WarcraftForeverCatalogNamespace {
Catalog: CatalogSdk & { createClient(options?: CatalogClientOptions): CatalogSdk; version: string };
Classes: CatalogSdk['Classes'];
Races: CatalogSdk['Races'];
Factions: CatalogSdk['Factions'];
Pets: CatalogSdk['Pets'];
}
export type DataResource = 'items' | 'recipes' | 'professions' | 'spells' | 'references';
export interface DataManifest {
schemaVersion: 1;
defaultChannel: string;
channels: Record<
string,
{
build: string;
product: string;
locales: string[];
datasets: Record<DataResource | 'talents' | 'legacy' | 'catalog', string>;
}
>;
limits: { batch: number; search: number; defaultSearch: number };
unavailable: Record<string, string>;
}
export interface DataEnvelope<T> {
schemaVersion: 1;
dataset: string;
locale: string;
resource: DataResource;
source: { build: string; product: string; observedAt?: string; access?: string };
coverage: string;
data: T;
}
export type DataBatch<T> = DataEnvelope<T[]> & { missingIds: number[] };
export type DataPage<T> = DataEnvelope<T[]> & { nextCursor: string | null };
export interface SpellVariant {
kind: 'talent' | 'legacy' | 'recipe';
description: string | null;
descriptionLocale: string | null;
recipeId?: number;
calculator?: string;
revision?: string;
treeId?: string;
talentId?: string;
rank?: number;
details?: string[];
scaling?: { level: number | null; gear: false; buffs: false };
castMs?: number | null;
cooldown?: { ms: number; category?: number } | null;
}
export interface PublicSpell {
id: number;
name: string | null;
nameLocale: string | null;
iconUrl: string;
url: string;
undiscovered?: boolean;
variants: SpellVariant[];
}
export interface ReferenceValue {
id: number;
name: string;
nameLocale: string;
color?: string | null;
inferred?: boolean;
}
export interface ReferenceCatalog {
equipmentSlots: ReferenceValue[];
statLabels: ReferenceValue[];
schools: ReferenceValue[];
itemClasses: ReferenceValue[];
qualities: ReferenceValue[];
powerTypes: ReferenceValue[];
classColors: Array<{ slug: string; color: string; source: 'site-presentation' }>;
}
export interface DataCollection<T> {
get(id: number, options?: PublicDataOptions): Promise<DataEnvelope<T | null>>;
getMany(ids: number[], options?: PublicDataOptions): Promise<DataBatch<T>>;
search(query: string, options?: PublicDataSearchOptions): Promise<DataPage<T>>;
}
export interface DataClient {
manifest(): Promise<DataManifest>;
Items: DataCollection<PublicItem>;
Recipes: DataCollection<PublicRecipe>;
Professions: DataCollection<PublicProfession>;
Spells: DataCollection<PublicSpell>;
References: { list(options?: PublicDataOptions): Promise<DataEnvelope<ReferenceCatalog>> };
}
export interface DataClientOptions extends PublicDataOptions {
origin?: string;
/** Custom JSON transport; images and site URLs still use their own sources. */
reader?: JsonReader;
}
export type PublicDataResource = 'items' | 'recipes' | 'professions';
export interface PublicRecordName {
name: string | null;
nameLocale: ClientLocale | null;
url?: string;
iconUrl?: string;
}
export interface PublicItem extends Item, PublicRecordName {
effects: ItemEffectLine[];
flavor: string | null;
}
export interface PublicRecipe extends Recipe, PublicRecordName {
description: string | null;
}
export interface PublicProfession extends Omit<Profession, 'categories'>, PublicRecordName {
categories: Array<Category & PublicRecordName>;
}
export interface PublicDataRecords {
items: PublicItem;
recipes: PublicRecipe;
professions: PublicProfession;
}
export interface PublicRecordBatch<T> {
/** Unique existing records in request order. */
data: T[];
missingIds: number[];
}
export interface PublicRecordPage<T> {
data: T[];
nextCursor: string | null;
}
export interface PublicDataOptions {
channel?: string;
/** Immutable dataset returned by discovery or an earlier response. */
dataset?: string;
locale?: string;
}
export interface PublicDataSearchOptions extends PublicDataOptions {
limit?: number;
cursor?: string;
/** Undiscovered client records are excluded from search by default. */
includeUnknown?: boolean;
}
export interface CalculatorDescriptor {
data: string;
revision: string;
revisions: Record<string, string>;
}
export interface CalculatorClientConfig {
origin: string;
definitions: Record<string, CalculatorDescriptor>;
/** Immutable dataset descriptor published by Warcraft Forever. */
dataset?: string;
}
export interface CalculatorClientOptions {
origin?: string;
dataset?: string;
}
export interface TalentRequest {
class: string;
revision?: string;
maxLevel?: number;
}
export interface LegacyRequest {
revision?: string;
}
export type BuildInput = { code: string; allocation?: never } | { allocation: TalentAllocation; code?: never };
export type BuildFormat = 'blizzard' | 'compact' | 'structured';
export interface CalculatorValidation {
valid: boolean;
revision: string;
dataset?: string;
pointBudget: number;
pointsSpent: number;
pointsRemaining: number;
complete: boolean;
issues: TalentIssue[];
allocation: TalentAllocation;
}
export type CalculatorEncoded<Build = StructuredTalentBuild | StructuredCalculatorBuild> = CalculatorValidation &
({ format: 'blizzard' | 'compact'; code: string } | { format: 'structured'; build: Build });
export interface CalculatorDiff extends TalentAllocationDiff {
revision: string;
dataset?: string;
pointBudget: number;
before: CalculatorValidation;
after: CalculatorValidation;
}
export interface CalculatorClient<Request, Definition extends TalentDefinition, Build> {
definition(options: Request): Promise<Definition>;
decode(options: Request & { code: string }): Promise<CalculatorValidation>;
encode(
options: Request & { allocation: TalentAllocation; format: 'structured' }
): Promise<CalculatorValidation & { format: 'structured'; build: Build }>;
encode(
options: Request & { allocation: TalentAllocation; format?: 'blizzard' | 'compact' }
): Promise<CalculatorValidation & { format: 'blizzard' | 'compact'; code: string }>;
encode(options: Request & { allocation: TalentAllocation; format?: BuildFormat }): Promise<CalculatorEncoded<Build>>;
validate(options: Request & BuildInput): Promise<CalculatorValidation>;
diff(
options: Request & { before: TalentAllocation | string; after: TalentAllocation | string }
): Promise<CalculatorDiff>;
}
export type TalentsClient = CalculatorClient<TalentRequest, ClassicTalentDefinition, StructuredTalentBuild>;
export interface LegacyClient extends Omit<
CalculatorClient<LegacyRequest, CalculatorDefinition, StructuredCalculatorBuild>,
'definition'
> {
definition(options?: LegacyRequest): Promise<CalculatorDefinition>;
}
export interface StructuredBuild {
revision: string;
pointsSpent: number;
trees: {
id: string;
name: string;
pointsSpent: number;
talents: {
id: string;
name: string;
rank: number;
maxRanks: number;
row: number;
column: number;
icon: string;
spellId?: number;
description?: string;
details?: string[];
}[];
}[];
}
export interface StructuredTalentBuild extends StructuredBuild {
class: string;
}
export interface StructuredCalculatorBuild extends StructuredBuild {
id: string;
}