Changelog

Features, fixes, and breaking changes in Fluxer.js.

v3.3.0

GitHub

Threads, forum channels, and media channels: create them, post in them, and follow gateway updates.

Features

  • ThreadChannel, ForumChannel, and MediaChannel

    Channel types 10, 11, and 12 are threads. Types 15 and 16 are forum and media parents. Create them with tags, sort, reaction, and layout. Edit those on the channel, or change one tag with createTag, editTag, and deleteTag. Posts use threads.createPost. Text and announcement channels use threads.create. message.startThread starts a thread from an existing message. guild.fetchActiveThreads lists active threads. Ready, Guild Create, and Guild Sync cache the threads array. Gateway events emit threadCreate, threadUpdate, threadDelete (including a thread that was not cached), threadListSync, threadMemberUpdate, threadMembersUpdate, threadMemberListUpdate, and forumUnreads. requestForumUnreads sends opcode 28. subscribeThreads sends the lazy-request thread options.

Packages

  • `@fluxerjs/core` 3.3.0
  • `@fluxerjs/types` 3.3.0

v3.2.0

GitHub

Guild announcement channels (type 5): create them, detect them, and convert a text channel.

Features

  • ChannelType.GuildAnnouncement is 5

    AnnouncementChannel is text-capable (send, topic, slowmode). isAnnouncement() is true only for type 5. isText() stays type 0. edit({ type }) converts between GuildText and GuildAnnouncement.

Packages

  • `@fluxerjs/core` 3.2.0
  • `@fluxerjs/types` 3.2.0

v3.1.0

GitHub

GuildCreate matches Discord.js (startup backfill is GuildAvailable), REST uses api_public and unversioned discovery, audit reason headers, voice connection_id / VOICE_STATE_ACK, and Snowflake deconstruct field renames. Upgrade steps: /guides/upgrading-to-3-1/.

Breaking Changes

  • GuildCreate is for joins after Ready only

    READY unavailable stubs land in client.guilds with available === false. Full startup / reconnect snapshots emit GuildAvailable, not GuildCreate. Set ClientOptions.emitGuildCreateOnStartup: true to keep the old startup GuildCreate behavior.

  • REST host is api_public, not api

    Bot REST calls use endpoints.api_public. Hosted defaults keep api / api_client on https://web.fluxer.app/api and api_public on https://api.fluxer.app. Client.fromDiscovery and fetchInstance use unversioned GET {origin}/.well-known/fluxer.

  • SnowflakeUtil.deconstruct uses sequence

    The bit layout exposes sequence instead of increment. processId is always 0 (deprecated alias). Update any code that read increment.

  • Voice leave needs connection_id

    Leave and mute updates send connection_id. Leave without it throws VOICE_MISSING_CONNECTION_ID. Joins send mutation_id; a rejected VOICE_STATE_ACK fails the pending join.

  • Identify omits intents; e2ee_capable defaults true

    Shards no longer send intents on Identify. properties.e2ee_capable defaults to true; pass e2eeCapable: false to opt out. Identify types no longer include compress or large_threshold.

  • Bot attachment uploads max 50 MiB

    Files larger than BOT_ATTACHMENT_MAX_BYTES fail before PUT with AttachmentTooLarge / ATTACHMENT_TOO_LARGE.

  • CDN size is snapped to the asset ladder

    CdnUrlOptions.size is snapped via snapCdnSize / CDN_SIZE_LADDER and clamped by asset class (icon, banner, emoji, sticker). Arbitrary exact sizes no longer pass through.

  • Administrator skips channel overwrites in computePermissions

    If base roles include Administrator, computePermissions returns the full mask and does not apply channel overwrites (owner path unchanged).

Features

  • emitGuildCreateOnStartup ClientOption

    Legacy escape hatch when bots still seed state from GuildCreate on every restart. Prefer GuildAvailable or Ready + client.guilds for new code.

  • Audit reason on moderation mutations

    Optional reason on channel edit/delete, member edit, ban/kick/unban, roles, emojis, stickers, and message delete. Sent as X-Audit-Log-Reason (not JSON body).

  • ClientOptions.locale / RESTOptions.locale

    Sent as Accept-Language (default en-US / DEFAULT_LOCALE). Client locale overrides rest.locale when both are set.

  • waitForGuilds settle timer resets as guilds arrive

    Empty READY streams no longer finalize Ready mid-stream when guilds keep arriving within GUILD_STREAM_SETTLE_MS. The same window classifies those snapshots as GuildAvailable even when waitForGuilds is off (Ready still fires immediately).

  • Message.nsfwEmojis / API nsfw_emojis
  • CDN helpers accept size for emoji and sticker URLs; export CDN_SIZE_LADDER and snapCdnSize

Fixes

  • Voice play() no longer pins a CPU core after a track ends

    The LiveKit demuxer end handler now yields with setTimeout and bails out when playback stopped or the source closed, instead of busy-waiting on setImmediate. parseOpusPacketBoundaries passes complete packets to libopus instead of splitting TOC codes, which stalled decode on code 3 packets.

Packages

  • `@fluxerjs/core` 3.1.0
  • `@fluxerjs/rest` 3.1.0
  • `@fluxerjs/types` 3.1.0
  • `@fluxerjs/util` 3.1.0
  • `@fluxerjs/voice` 3.1.0
  • `@fluxerjs/ws` 3.1.0

v3.0.0

GitHub

DX overhaul: domain wrappers with native methods (fetch then channel.send / channel.delete), PartialMessage / PartialGuildMember for uncached deletes and removes, camelCase nested message fields, collector bounds, AttachmentBuilder, process sharding packages, and @fluxerjs/core/internal for wire serializers. Also removes expression packs, CompactAttachments, pack invite types, and the old overwrite helpers. Upgrade steps: /guides/upgrading-to-3/.

Breaking Changes

  • MessageDelete is PartialMessage, not Message

    The payload has id, channelId, guildId, channel, content, authorId, and author / createdAt when the message was cached. It has fetch() and resolveChannel(). It does not have edit, reply, or react. Use message.partial or Message.resolve(msg) when a handler accepts both Message and PartialMessage.

  • Uncached MessageUpdate is PartialMessage

    Cached edits still emit Message. Uncached edits emit PartialMessage. Check message.partial before reading content, author, or calling reply.

  • GuildMemberRemove is GuildMember | PartialGuildMember

    If the member was not in cache, you get a PartialGuildMember class (partial: true) with id, guildId, user, and guild. Check member.partial or fetch before using nick, roles, or kick.

  • MessageDeleteBulk includes messages: PartialMessage[]

    The bulk payload still has the channel and ids. messages is the per-id list so you can fetch or inspect cached content without a second lookup.

  • Channel.send and Channel.delete are on Channel

    client.channels.fetch(id) and message.resolveChannel() return Channel with those methods. send is on text-capable channels (guild text, guild voice, DMs). Category and link channels do not have send. You do not need a GuildChannel cast or a raw REST Routes.channel call for the usual case.

  • message.channel is a text-capable channel or null

    When the channel is in cache it is already a text-based structure (including voice), so reply and send type-check. When it is missing, it is null; call message.resolveChannel() then send or delete.

  • Nested attachment, embed, and invite fields are camelCase

    Attachments are MessageAttachment (proxyUrl, contentType). Embeds are MessageEmbed (iconUrl, proxyUrl, htmlWidth). messageReference, invite snapshots, and call.endedAt follow the same rule. Sending still uses EmbedBuilder. If you compared snake_case fields on received messages, update those reads.

  • isTextBased() and isGuild() use ChannelType

    They do not duck-type `'send' in this` or `'guildId' in this`. Custom subclasses that faked methods without setting type will fail the guard.

  • Sweep filters receive Message, not raw APIMessage

    client.cache.sweepMessages((msg) => …) sees createdAt as a Date and the usual Message getters. Internal storage is still the wire object.

  • Guild.createChannel options are camelCase

    Use parentId and rateLimitPerUser (not parent_id / rate_limit_per_user). Same camelCase rule as guild.edit and channel.edit in 2.x.

  • login() returns this

    Was Promise<string> (the token) in older lines. Chain login or ignore the resolved value.

  • ClientOptions.intents is deprecated and ignored

    Fluxer does not use Discord-style gateway intents. Omit intents; use ignoredEvents to suppress specific dispatches.

  • Collectors require time and/or max

    CollectorOptionsRequired. By default awaitMessages / awaitReactions reject on idle (CollectorIdle) and max (CollectorMax). Pass errors: ["time"] when max should resolve successfully.

  • message.reactions is MessageReactionManager

    Use .cache for the reaction collection. Not a bare array or map of wire reaction objects.

  • message.flags is MessageFlagsBitField

    Not a raw number. Same bitfield pattern as permissions.

  • AttachmentBuilder(file, { name }) requires a name

    Pass a Buffer or URL plus options.name, then files: [builder] on send / reply / edit.

  • Invite snapshots replace live guild/channel fields

    Metadata is guildSnapshot / channelSnapshot. Call resolveGuild() or resolveChannel() for live structures.

  • embeds: [] on edit clears embeds

    Omit the embeds field to leave existing embeds unchanged.

  • Wire serializers moved to @fluxerjs/core/internal

    Prefer structure methods on domain objects. Import @fluxerjs/core/internal only when calling REST with raw camelCase→wire helpers. Pack* serializers are gone with the pack API.

  • client.packs and Pack* types are removed

    PackManager, PackCreateOptions, PackInvitePayload, and the rest of the pack exports are gone. Use guild emoji/sticker CRUD (createEmoji, cloneEmoji, createSticker). InviteType.EmojiPack and InviteType.StickerPack are also gone; invites are guild or group-DM only.

  • MessageFlags.CompactAttachments and MT_* guild features are removed

    CompactAttachments is no longer a MessageFlags member. GuildFeature no longer includes MT_MESSAGE_SCHEDULING or MT_EXPRESSION_PACKS. Align flag checks with the current OpenAPI snapshot.

  • channel.permissionOverwrites is a PermissionOverwriteManager

    It is no longer APIChannelOverwrite[]. channel.editPermission and channel.deletePermission are gone. Use channel.permissionOverwrites.edit(id, { type, allow, deny }) and .delete(id). Iterate .cache or the manager itself.

  • message.stickers are MessageSticker, not APIMessageSticker

    Each sticker is a domain object with name, tags, animated, and url. messageSnapshots use the same camelCase wrappers as the parent message.

  • GuildRoleUpdate and GuildRoleDelete are positional, not one payload object

    2.2 emitted ({ oldRole, role }) and ({ role, guildId, roleId }). 3.0 emits (oldRole, role) and (role, guildId, roleId). oldRole / role are null when the role was uncached. GuildRoleCreate is still a single Role.

Added

  • @fluxerjs/sharding: ShardingManager, one process per shard

    IPC between parent and children, ShardClientUtil inside the bot file, and a parent identify throttler. See examples/sharded-bot.js and /guides/sharding/.

  • @fluxerjs/sharding-redis: Redis coordinator and session store

    Optional Redis-backed shard leases and gateway session persistence for multi-host deployments. Pair with @fluxerjs/ws session store APIs.

  • client.ws.ping and in-process sharding helpers on @fluxerjs/ws

    Heartbeat ACK RTT (-1 before the first ACK). Sharded clients expose an average and client.ws.getShard(id)?.ping. Also SimpleShardingStrategy, WorkerShardingStrategy, IdentifyThrottler, and SessionStore.

  • client.uptime

    Milliseconds since Ready, or null before login completes.

  • PartialMessage and PartialGuildMember

    Public types with partial: true, fetch() (messages), and the ids the gateway actually sent. Message.resolve() turns Message | PartialMessage into a full Message when you need one.

  • channel.awaitMessages and message.awaitReactions

    Promise wrappers over the existing collectors. Require time and/or max. By default, idle and max reject with CollectorIdle / CollectorMax; pass errors: ["time"] when max should resolve successfully.

  • GuildMember.kick, ban, and timeout

    Methods on the member you already have from message.member or guild.members.fetch. message.member is a getter onto the guild member cache.

  • users.resolve and Invite.resolveGuild / resolveChannel

    Resolve a user from cache by id or a User-like value. Invite metadata is guildSnapshot / channelSnapshot; call resolveGuild() or resolveChannel() for live structures.

  • role.has(permission)

    Alias of role.permissions.has for shorter permission checks.

  • Reaction.fetchMessage() is cache-first, then REST
  • isGuild / isText / isCategory / isVoice / isLink on Channel
  • PermissionOverwrite / PermissionOverwriteManager and GuildRoleManager (guild.roles.everyone)

Changed

  • Prefer channel.send and channel.delete

    Guides and examples use const channel = await client.channels.fetch(id) then channel.send / channel.delete. client.channels.send, message.sendTo, and REST Routes.channel are still there if you only have an id. Manager .delete() is still cache-only (it does not hit HTTP).

  • Collectors still unwrap reaction events

    Gateway messageReactionAdd is one payload object. Collectors still call your filter and collect handlers as (reaction, user). Leave collector callbacks alone unless you dropped collectors.

  • Role event shapes stay structure-first

    GuildRoleCreate still emits Role. GuildRoleUpdate and GuildRoleDelete switched from a single payload object in 2.2 to positional arguments in 3.0 (see Breaking Changes).

  • examples/sharded-bot.js added. Other examples use fetch, member.kick, AttachmentBuilder, and resolveChannel.

Fixed

  • Uncached deletes no longer type-check as if author and createdAt always exist.
  • Channel methods exist on the same type client.channels.fetch returns.
  • Guild voice channels are text-capable: isTextBased(), send, and message.channel.
  • Guild channel cache indexing uses isGuild() instead of a guildId own-property check.
  • OpenAPI nick / communication_disabled_until optionality already matched Fluxer; no extra type churn from fluxerapp/fluxer#1522
  • REST retries honor Retry-After as a fallback

    On 429, the client waits JSON retry_after when present, otherwise the Retry-After header (including non-JSON bodies). Retryable 5xx uses Retry-After when set, otherwise the usual backoff.

Docs

  • Dedicated Upgrading to 3.0 guide with the DX breaking-change index (/guides/upgrading-to-3/). Migrating to 2.0 stays at /guides/migration/.
  • Voice channels are documented as text-capable. Bots should use preloadMessages instead of bulkFetchMessages.
  • Expression packs are no longer advertised; guild emoji/sticker CRUD remains.
  • Sharding guide covering ShardingManager, worker strategy, and Redis coordination (marked advanced).
  • Getting Started path: install → basic bot → prefix → errors → caching. Upgrading guides sit in their own sidebar category.
  • Channels, sending, events, collectors, attachments (AttachmentBuilder), and moderation guides updated for 3.0 types.
  • Where do I...? task index, content warnings, server discovery (apply, not join), users/FriendlyBot, forwards, vanity URLs, emoji clone/bulk, and history-bot.
  • Guides index groups now have blurbs and a task jump list. Search indexes optional guide searchTerms.

v2.2.0

GitHub

A large client cache rebuild (LimitedCollection + client.cache), Tenor replaced with Klipy for GIF helpers, and a refreshed OpenAPI snapshot with tighter types. Most other breaking changes are import-path moves and small behavior tweaks; the migration notes below walk through each one.

Breaking Changes

  • Tenor GIF helpers replaced with Klipy

    resolveTenorToImageUrl and related Tenor exports are removed. Use resolveKlipyToImageUrl (and KlipyMediaResult) from @fluxerjs/core or @fluxerjs/util. Fluxer’s unfurler expects Klipy page URLs in message content; see /guides/gifs/.

  • Disabling the message cache now uses messages: false, not messages: 0

    To match the rest of the cache options, a numeric 0 (and other documented numeric zeros) now means "unbounded / no limit" instead of "off". If you passed cache: { messages: 0 } to turn message caching off, it will now cache every message. Use cache: { messages: false } to disable it.

  • Deep file imports moved; import from package entrypoints instead

    Internal layout was reorganized (guild/message structures, sdk options modules, util helpers). Paths like @fluxerjs/core/structures/Guild.ts no longer resolve. Import public names from the package root or documented subpaths (@fluxerjs/core, @fluxerjs/core/client, @fluxerjs/core/message, @fluxerjs/core/cluster, @fluxerjs/core/errors).

  • formatEmoji custom output is now name:id (previously :name:id)

    The leading colon was dropped from the custom-emoji form, and unicode emoji are no longer passed through encodeURIComponent. Update any code that parsed or compared the old :name:id shape.

  • Animated emoji identifiers now include the a: prefix (a:name:id)

    GuildEmoji.identifier and MessageReaction.emojiIdentifier return a:name:id for animated emoji so they round-trip correctly through the API. Static emoji are unchanged and still return name:id.

  • message.react() always emits a local messageReactionAdd after the REST call

    Once the reaction request succeeds, the client emits messageReactionAdd with a structured emoji object ({ name, id?, animated? }) instead of putting the raw wire string into emoji.name. Your listeners and collectors now see the same shape whether the reaction came from you or from the gateway.

  • EmbedBuilder.toJSON() always includes description (null when unset)

    The description field was previously omitted when it had no value. If you snapshot or diff embed JSON, expect an explicit description: null to appear.

  • WebSocket identify intents is now optional and defaults to 0

    Fluxer ignores the legacy Discord-style intents value, so you can drop it entirely. If your code required intents: N, pass intents: 0 or simply omit it.

  • Stricter request validation may reject bodies that used to be accepted

    The vendored OpenAPI snapshot was refreshed against live Fluxer, which tightened nullability on some channel and guild request fields. Payloads that were loosely typed before may now be rejected; align them with the refreshed snapshot.

Added

  • LimitedCollection: a bounded cache with FIFO eviction (@fluxerjs/collection)

    A drop-in Collection that enforces a maximum size, evicting the oldest entries first, with an onEvict callback so you can react when items are dropped.

  • client.cache controller for inspecting and clearing caches

    Read cache stats, run manual sweeps, and cascade-teardown related data across guilds, channels, members, messages, roles, emojis, and stickers from one place.

  • Identity-preserving guild snapshots on READY / GUILD_CREATE

    When a guild is re-sent, its nested caches (members, channels, roles, and so on) are retained on the same guild instance instead of being rebuilt, so existing references stay valid.

  • resolveKlipyToImageUrl for embedding Klipy GIFs

    Resolves a klipy.com page URL to a direct media URL (and animated flags) for EmbedBuilder.setImage. Send the page URL as message content when you want the native gifv unfurl instead.

  • ClientEvents payload types exported from @fluxerjs/core

    Every camelCase ClientEvents payload type (including helpers like GuildRoleUpdatePayload) is public, plus ClientEventName and ClientEventListener for typed listeners.

  • ClientUser.fetch() and ClientUser.fetchGuilds({ withCounts }) for refreshing the current user and its guilds
  • client.requestGuildMembers() gateway helper for requesting member chunks
  • CHANNEL_RECIPIENT_ADD / CHANNEL_RECIPIENT_REMOVE handlers and matching client events
  • channels.fetch(id, { force }) and matching force/patch behavior on guilds.fetch
  • GUILD_COUNTS_UPDATE now updates guild.memberCount and guild.onlineCount live
  • New WebSocket identify options: flags, ignoredEvents, and initialGuildId
  • OpenAPI gateway coverage, alignment asserts, and Fluxer compare scripts (pnpm openapi:check / openapi:assert)
  • New cache-bot example and an expanded caching guide

Changed

  • Internal package layout was reorganized; public imports stay on package entrypoints (see Breaking Changes)
  • Managers now run on LimitedCollection with cache limits resolved from your options at construction time
  • Unhandled gateway dispatches now emit Events.Debug

    Dispatches the client does not handle surface as a Debug event so they are visible, except session, user, and call events, which stay intentionally unhandled and quiet.

  • Voice join and move now send explicit mute/deaf/self_* defaults in the state update
  • The vendored OpenAPI snapshot was refreshed against live Fluxer and the types package was aligned to it
  • Gateway guild create/ready snapshot typing uses GatewayGuildSnapshot (nested properties plus top-level count fields)

Fixed

  • Guild memberCount / onlineCount hydrate from GUILD_CREATE and COUNTS_UPDATE

    Fluxer puts member_count / online_count on the guild snapshot root (alongside properties). The client merges those top-level fields, keeps prior counts when a later payload omits them, and applies GUILD_COUNTS_UPDATE live.

  • Synthetic react() events no longer stuff the full wire string into emoji.name
  • ReactionCollector no longer double-counts repeated self/gateway events for the same user and emoji
  • CHANNEL_DELETE now cleans up guild-indexed channels after a global FIFO eviction
  • Channel-create rate_limit_per_user and NSFW/CWL nullability now match the OpenAPI schema
  • Multipart / undici form serialization from the earlier 2.x patch line is carried forward in this release

Migration notes

  • Replace resolveTenorToImageUrl / Tenor imports with resolveKlipyToImageUrl (see /guides/gifs/)
  • Change cache: { messages: 0 } (used to mean "off") to cache: { messages: false }
  • Replace deep structure imports with named imports from the root, e.g. import { Guild, MessageReaction } from "@fluxerjs/core"
  • If you matched reactions on emoji.name === "name:id", switch to emoji.id or emojiIdentifier
  • If you passed ws intents: N, change it to intents: 0 or drop it (Fluxer ignores intents)

v2.1.0

GitHub

Changed

  • Require Node.js ≥ 22.13 across all published packages
  • REST no longer sends the configured token to cross-origin absolute URLs unless auth: true is set explicitly
  • Automatic retries now apply only to safe methods (GET/HEAD/OPTIONS) by default; opt mutations in per-request via retryPolicy
  • Client now honors configured cache limits (managers are constructed after resolving DEFAULT_CACHE_LIMITS with your options)

Fixed

  • Reactions with external custom emojis are now allowed when permitted by Fluxer
  • Preserve cached reaction users across gateway events
  • Correct REST rate-limit bucket identification
  • Clean up timed-out voice connections
  • Hydrate and harden normalization of nested gateway guild properties

v2.0.3

GitHub

Fixed

  • ReactionCollector now handles messageReactionAddMany (gateway-batched reactions), not only messageReactionAdd

v2.0.2

GitHub

Added

  • REST retryPolicy for per-request retry budgets (e.g. keep retries on GET, disable on writes)
  • Guild.available plus GuildUnavailable / GuildAvailable for temporary gateway outages

Fixed

  • User.prototype.send argument typing
  • Temporary guild unavailability no longer emitted as GuildDelete

Docs

  • Versioned SDK reference and guides for each tagged release (v2.0.0+)
  • Website improvements

v2.0.1

GitHub

Changed

  • Raised Node.js engine requirement (Node 20 support removed; requires Node ≥ 22.13)
  • Updated undici to v7

Fixed

  • Dependency audit fixes
  • Publishing and CI improvements, including bot-login checks

v2.0.0

GitHub

Major rewrite (non-voice)

  • ChannelType.GuildLink is 998; createChannel uses ChannelCreateRequest (link requires url)
  • EmbedBuilder: removed setVideo/setAudio; request type RESTPostAPIEmbed separate from APIEmbed
  • Default bounded caches (DEFAULT_CACHE_LIMITS); reply defaults; GUILD_UPDATE preserves caches
  • Reaction events emit full ClientEvents arity; Collection first/last/random allocation fixes
  • Interactions / slash commands removed (not in OpenAPI)
  • See the Migrating to 2.0 guide