Recipes

Override a Built-in Structure

Replace a default class with your own subclass and keep everything strongly typed.

Hoshimi builds every core object through a Structures factory. Swap an entry for your own subclass to add methods or state, and augment CustomizableStructures so the types follow.

The override pattern

custom-player.ts
import { ,  } from 'hoshimi';

class  extends  {
  public (): boolean {
    return this. > 120;
  }
}

. = (...) => new (...);

declare module 'hoshimi' {
  interface CustomizableStructures {
    : ;
  }
}

The augmentation is what re-types everything

Assigning to Structures swaps the class at runtime; augmenting CustomizableStructures is what makes player.filterManager, PlayerStructure and every other reference resolve to your subclass. A local interface CustomizableStructures in your file does nothing — it has to be inside declare module 'hoshimi'.

Override several at once

multi-override.ts
import { , , ,  } from 'hoshimi';

class  extends  {}
class  extends  {}
class  extends  {}

. = (...) => new (...);
. = (...) => new (...);
. = (...) => new (...);

declare module 'hoshimi' {
  interface CustomizableStructures {
    : ;
    : ;
    : ;
  }
}

Every structure is overridable: Player, Node, NodeManager, Rest, Queue, LyricsManager, FilterManager, Track, UnresolvedTrack, PlayerVoiceState and PlayerStorageAdapter.

Use the structure types

The *Structure type aliases resolve to the final instance type — default or your override — so use them in signatures instead of the concrete class.

structure-types.ts
import type { ,  } from 'hoshimi';

function (: ): void {
  // Works with the default Player or any custom replacement.
  .(`Guild: ${.}`);
}

function (: ): string {
  return ..;
}

Best practices

  • Override structures once during bootstrap, before creating any managers.
  • Keep constructor signatures unchanged to stay compatible with the factory.
  • Add narrow, domain-specific methods instead of rewriting core flow logic.
  • Use *Structure types in signatures to avoid coupling to concrete classes.

Typing data, not classes

To type the requester/userData on tracks or the keys of player.data you do not need a subclass — see Type your track requester and Type player.data keys.