Warcraft Forever Developers
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;
}
Warcraft Forever is an independent fan site, not affiliated with Blizzard Entertainment.