v1.9.0 — First-Class NodeLink
NodeLink node type and capability system, mixed pools, server-side plugin filters, youtube-source config, Player.skipTo, and a zero-warning lint pass.
Released 2026-09-20 · npm install yukumo@1.9.0
Connect to Lavalink, NodeLink, or both at once in one pool with the same API. Each node now reports what it can do, so NodeLink-only features are routed to a capable node and never fail silently on a plain Lavalink node.
Added
First-class NodeLink support
NodeConfig.type: "lavalink" | "nodelink" | "auto"(default"auto", detected from/v4/info). The legacyisNodeLinkflag still works. NewcreateNodeLinkNode()/createLavalinkNode()config helpers.node.type,node.version,node.capabilities,node.sourceManagers— resolved on ready.
import { YuKumo, createLavalinkNode, createNodeLinkNode } from "yukumo";
const kumo = new YuKumo({
nodes: [
createLavalinkNode({ host: "ll", port: 2333, password: "x" }),
createNodeLinkNode({ host: "nl", port: 2333, password: "x" }),
],
send: (guildId, payload) => client.guilds.cache.get(guildId)?.shard.send(payload),
});Capability system
node.supports(feature), node.supportsFilter(name), and node.assertSupports(feature) over a
typed NodeFeature union. Feature requests are routed to a capable node, and a mismatch throws a
typed error instead of failing silently.
node.supports("voiceReceive"); // true on NodeLink
const n = kumo.nodes.pick(guildId, { feature: "lyrics" }); // capability-aware pick- New
YukumoUnsupportedFeatureError(code: "UNSUPPORTED_FEATURE") withfeature,nodeId,nodeType. - Cross-family failover:
Player.setNode()drops filters the target node can't run (NodeLink extras onto Lavalink) with a debug log — audio keeps playing rather than erroring.
Server-side plugin filters
player.setPluginFilter(name, settings) (and FilterChain.setPluginFilter() /
getPluginFilter() / hasPluginFilter()) pass arbitrary filter-plugin settings through Lavalink
v4's pluginFilters — works with LavaDSPX and any filter plugin. Pass false/null to remove.
await player.setPluginFilter("normalization", { maxAmplitude: 0.75, adaptive: true });youtube-source config
node.rest.getYouTubeStatus(), setYouTubePoToken(poToken, visitorData), and
setYouTubeRefreshToken(refreshToken?, skipInitialization?) drive the youtube-source plugin's
poToken / OAuth to bypass YouTube bot-detection.
Player.skipTo
player.skipTo(index) jumps to a queue index and starts it on the node, so the queue cursor never
points at a not-yet-played track while old audio is still running. Skipped-over tracks go to
history.
Typed server-plugin markers
createLavaSrcPlugin() / createSponsorBlockPlugin() / createFloweryTTSPlugin() return
introspectable markers (isServerPlugin, recorded options) so manager.plugins.get(name) can
confirm the plugin is expected.
Changed
- Zero-warning lint — the WebSocket client, framework adapters, REST client, and lyrics client
no longer use
any; a sharedisVoicePacket()guard replaces per-adapter packet checks. The manager's event forwarding is fully typed against the publicEventMap.
Fixed
- Concurrent
WebSocketClient.connect()returns the same in-flight promise, so all callers await the real socket open (or failure) instead of resolving optimistically. - Stale-socket handler guards, timed-out-connect abort,
close()keeps manager wiring, andunref()'d reconnect timers (a dead node no longer holds the process alive). - Node failover is deferred during a resuming node's window; a removed playing track is held as
"current" until it ends;
previous()no longer duplicates in queue-repeat;noReplacekeeps the position baseline; a paused-start track stays paused;resolveTrackhonors the playlistselectedTrack;Track.toJSON()round-trips therequester.