Channel, Guild, Message, Send, User, Count, Bot, Message, Attachment, OAuth, Instance
Changelog
What changed in each release of the Fluxerly.js SDK, newest first
Unreleased
These changes are in this nightly and not yet in a Stable release
Major Changes
-
Require Effect 4.1.0 or newer. The
effectpeer range is now^4.1.0, so every supported Effect release reports combined failures the same way. Since Effect 4.0.6, an expected failure that occurs together with a defect, such as a cleanup fault, is reported as a defect that keeps the failure's value. In the default API, such anSdklists the original error as aDefect Defectreason instead of aFailurereason, while its cause, message and code still come from the actual fault, such as the cleanup failure. In the native API, the Cause holds the original error as aDienext to the cleanup defect, socatchno longer recovers from it. The original error is still the public one, such asTag MessageorOperation Error Connection, never an internal REST or gateway failureError Migration: Update Effect to 4.1.0 or a later Effect 4 release. Code that read the
Failurereason of anSdkto find the original error should also look at itsDefect Defectreasons -
Breaking change for code that builds
Shardobjects by hand, such as test doubles ofState client: The new.shards sessionfield is required. AddEpoch sessionto such objects. The same applies to code that buildsEpoch: 0 Eventobjects by hand, such as tests that call handlers or middleware directly, which must now setContext sessionandEpoch receipt, and to code that buildsBotobjects by hand to callEvent Context runhandlers directly, which must now setBot context -
Breaking changes in
commands: A single quote is now an apostrophe unless it starts a word, and its rejections carry the command.parse Quoted name. Double quotes and backslash escapes are unchanged, and the rejections for an unclosed quote and a trailing backslash change only as described below. A single quote opens a quoted span only at the start of the text, after whitespace or directly after a span that just closed. Inside a single-quoted span, a single quote ends the span unless the next character is a letter or digit, so the end of the text, whitespace, quotes and punctuation all end it. Every other single quote is literal.Inputs that now parse differently, shown as the words each gives (arguments after
!say):don'tandit's Ada's: Nowdon't, thenit'sandAda's. Before, the apostrophe opened a span, sodon'twas rejected as an unclosed single quote andit's Ada'sgave the one wordits Adasa'b c'andkey='a b': Nowa'bwithc', andkey='awithb'. Before,ab candkey=a ba''b,a'b'canda': Now unchanged asa''b,a'b'canda'. Before,ab,abcand an unclosed single quote'it's fine','don't'and'x'y': Nowit's fine,don'tandx'y. Before, all three were rejected as an unclosed single quote'the dogs' bowl': Nowthe dogsandbowl'. Before, rejected as an unclosed single quote'a'b,'a b'c,''a,'a'1and'日本'語: Now rejected as an unclosed single quote, because the second quote is followed by a letter or digit and so stays inside the span. Before,ab,a bc,a,a1and日本語a' 'bandx'y 'z: Now rejected as an unclosed single quote, because the first apostrophe no longer opens a span and the later quote does. Before,a bandxy z
Inputs that parse as before include
'Yes', 'No'(Yes,andNo),'a'.,'a'-b,'a' 'b','a''b'(ab),don\'tand every input without a single quote.Rejections that
commandsreturns for an unclosed quote and a trailing backslash now also carry.parse Quoted name, the command name read before the mistake, so a reply can say which command it was for. A parse and a rejection can therefore both carryname, and a caller that told them apart with"name" in resultmust use"reason" in resultinstead.The
reasonof acommandsrejection for an unclosed single quote now explains the common cause when the open span held a single quote that a letter or digit followed, as in.parse Quoted 'a'b. The text then also says that a single quote before a letter or digit may have been meant to close the span, and suggests a space after that quote or double quotes. Thereasonfor a single quote without that cause, for a double quote and for a trailing backslash is unchanged, and the rejection keeps itsname. Code that comparedreasonwith the exact text of the first form must match the new text, or the shared startA single quote was opened but never closed.To migrate, remove workarounds such as
don\'t, which still works but is no longer needed. Wrap values that follow text in double quotes, such askey="a b", and end a single-quoted span before whitespace or punctuation, not before a letter or digit, or use double quotes, which may touch text, such as"a"b. A word that begins with an apostrophe, such as'twas, still opens a span, so write it as\'twas. -
Public type names no longer carry
DefaultorNativeprefixes, and the per-resource request option types becomeRequestandOptions AuditedRequest Options Each entry point exports a type that differs between the APIs under one name with its own shape, as
Clientalready did. In@neontechspace/,fluxerly RequesthasOptions signalandtimeout, and inMs @neontechspace/it hasfluxerly/ effect timeout.Ms AuditedaddsRequest Options auditin both. Notes that the removed option types carried, such as the per-page deadline ofReason messages, now sit on the methods that use them. The.iterate Search replyhelpers of default command and event contexts takeOmit<Request, because they apply the handler's signal. Runtime behavior is unchangedOptions, "signal"> Migration:
- Both
- Old name
Operation Options Operation Options Operation Options Options Operation Options Operation Options Application Operation Options Search Options Refresh Options Operation Options Resolve Options - New name
- Both
- Old name
Channel,Audit Operation Options Guild,Audit Operation Options Message,Audit Operation Options Moderation,Options WebhookOperation Options - New name
- Default
- Old name
Defaultplus any name in the two rows above - New name
- Default
- Old name
Default<Name>, such asDefault,Can Manage Options Default,Collector Options Default,Rest Request Default,Prefix Command Context Default,Supervisor DefaultorGuards DefaultCommands - New name
<Name> - Effect
- Old name
Native<Name>, such asNative,Prefix Command Context Native,Supervisor Native,Guards NativeorCommands NativeCommand Requirements - New name
<Name>
In the default entry point, names such as
Can,Manage Options Collector,Options Prefix,Commands Options SupervisorandChild Options Restnow name the default shape, which includesRequest signalwhere the oldDefaulttype did. The Effect entry point no longer exports the default-APIPrefixandCommands Options Supervisorshapes, and those names now refer to its ownChild Options
Minor Changes
-
Behavior change: A channel write no longer clears the whole channel cache. A write to a cached channel removes only that channel, plus its cached threads when the write deletes the channel or converts its type, and its cached forum posts that apply a tag the write removes, as
channelsand a.delete Forum Tag channelswith.edit availablecan, because Fluxer drops removed tags from posts without a thread update. Removing tags also stops pending reads from storing that forum's posts, while other pending reads by ID and reads of other communities still store what they return. A write to a category, and a write that names a community, such asTags channels,.create channelsor.reorder guilds, removes only that community's cached channels. A write to a channel the cache does not hold still clears every cached channel. Code that relied on the full clear can call.leave clientafter the write.cache .clear( "channels") Behavior change: A
guildevent for a forum or media channel now removes the channel's cached posts that apply a tag missing from the update'sChannel Update available, because Fluxer removes a tag through that update alone and drops it from posts on read. Fluxer gives each new tag a new ID, so posts whose tags all remain stay cached. Every such update also stops pending reads from storing that forum's posts, because a read of the forum can store its new tags before the update arrives. Other pending reads by ID and reads of other communities still store what they return, so an update in one community does not keep an unrelated read from filling the cache, while a pending read of the updated channel's own community stores nothing, as after any channel updateTags Behavior change: A
guildevent for a text or announcement channel now removes that channel's cached threads whose type the channel's type cannot hold, because Fluxer retypes a converted channel's threads and sends no thread update for archived ones. This works whether the channel was cached before the update or not, and after a read already stored its new type. Every such update also stops pending reads from storing that channel's threads, while other pending reads by ID and reads of other communities still store what they returnChannel Update Behavior change: A shard's first session now releases its communities' cached snapshots, as every later new session already did, and stops pending reads from storing them. Community, member, role, emoji, sticker and channel snapshots read through REST before connecting no longer outlast the first READY, because the events between that read and the session were never received. The release happens as READY arrives, before the communities Fluxer then sends again through
guild, which refill the community, role, emoji, sticker and channel caches. This also fixes later new sessions, which until now could release those communities after they arrived, leaving the caches empty until each community was read again. Read again after connecting to keep other snapshots, such as members. A saved session resumed throughCreate shardingis not a new session and keeps them.sessions The new
rolesmethod returns every cached role of a community, including everyone and ordered like.get All( guild Id) roles, or.fetch All undefinedunless the cache knows the list is complete. Aguildevent makes it complete, as does an uncontestedCreate rolesread that finishes while the community's shard is connected, because only role events keep the list current. A full read that finishes while the community's shard is disconnected therefore does not make the list complete, while a list that was already complete stays complete through a recovery that resumes the session. Role create and update events keep it complete. Removing any of its roles, a new session for its shard and every successful role write by the bot, including create, edit and reorder, or one whose outcome is unknown, make it unknown until the next.fetch All guildor full read. TheCreate rolescache limit counts the roles of every community together, so a bot in many communities needs a largermaxto keep complete listsEntries The new
membersmethod returns the bot's own cached member of a community, or.get Self( guild Id) undefinedwhen it is not cached or the bot's account ID is not yet known from READY or amembersresult. With.fetch Self cacheenabled, each.members guildnow stores the bot's own member. TheCreate guildevents sent while a session starts list member accounts by ID only, so the SDK completes the bot's own member from READY's account and leaves out the others. ACreate guildsent after startup, such as for a community the bot joins or one that becomes available again, lists complete members, and the SDK stores all of them, which are the bot's own and those in voice channelsCreate The new
Shardfield identifies a shard's current gateway session. It is 0 until the shard first starts a new session in this client, so a process that resumes a saved session after a restart reports 0 until that shard next starts a new session. It changes, without repeating, whenever the shard starts a new session, including after a move to a larger plan. A Resume keeps it. A changed value means that shard's community events may have been missed, and Fluxer then sends each community again throughState .session Epoch guild. It is the shard's current value, so an event still waiting in a handler's queue can already see a newer oneCreate The new
Eventfield gives the receiving shard'sContext .session Epoch sessionwhen the client accepted the event, so an event received before a new session keeps its earlier value even when its handler runs after the new session started. CompareEpoch contextper community, not per shard, because one shard serves many communities. Events delivered through the testing entry points carry the epoch of the test gateway's session in the same way.session Epoch The new
contextfield ofBotgives eachEvent Context runevent handler, in both APIs and in bots run byBot create, the sameTest Bot Eventthat aContext clienthandler receives, so a.on runbot can compareBot contextper community as well.session Epoch -
The
commandsnamespace has two new helpers in both APIs, and the quoted parser and its rejections changed. The newcommandsfunction returns the candidate closest to a misspelled name, using the rule behind the.suggest( name, candidates) suggestionofCommand: Case is ignored, one edit is allowed for names of up to four characters and two for longer ones, an adjacent swap counts as one edit, and the earliest of equally close candidates wins. It returnsUnknown Name undefinedwhen none is close and for an empty name. It agrees with the router for its default case-insensitive matching when given the registered names of one level, while the router also considers aliases and reports the command's own name for a near alias. The newcommandsfunction converts one word with any built-in argument descriptor except.parse Argument( token, descriptor, options?) custom, using the router's own grammar. It returns the descriptor's value type,undefinedfor a rejected word, and the descriptor'sdefaultorundefinedfor a missing one. The newCommandtype carriesArgument Parse Options guild, whichId memberdescriptors need. Invalid input to either helper, such as a bad descriptor, a non-string name or candidates that are not an iterable of strings, throwsConfigurationin both APIs, like other registration misuseError Prefixand theCommand Parse Rejection Commandoutcome ofParser Rejected onhave an optionalUnmatched name, the command name the parser read before it declined, as typed. Thecommandsparser sets it for an unclosed quote and a trailing backslash, a custom parser may set it, and the router passes it through, so a reply can say which command a syntax mistake was for. A name that is not command-name syntax is reported as a parser failure. Existing parsers and callbacks keep working, because the member is optional..parse Quoted -
Add
defineto the default and Effect entry points. It returns a bot'sBot runoptions without the token unchanged, and keeps the types of command arguments and, in the Effect API, of handler errors and services. A module can then export the options for bothBot runandBot createTest Bot The starter bot in
examples/now exports its options asstarter botwithdefine, answersBot !pingthrough thecommandsoption instead of matching the message text, and starts only when Node.js runs the file. It now also answers!pingfollowed by more text or in other letter cases. Thefluxerly initcommand writes these shipped files instead of its own copies, so the generated bot is the one the documentation shows, and its generated test importsbot. The repository examples, the quick start, the testing guide and the README use the same shape -
Behavior change: The default
maxof an iteration now grows withPages max, so a caller who sets onlyItems maxgets up to that many items. It is the pagesItems maxneeds at the page size plus 100 spare pages for short pages and skipped duplicates, instead of a fixed 100. For example,Items messageswith.iterate History maxand the default page size of 50 used to fail withItems: 10000 PaginationreasonError pageafter 5,000 items and now reads all 10,000. This applies toLimit iterate,History members,.iterate members,.iterate Search guilds,.iterate audit,Logs .iterate iterate,Reaction Users iterate,Pins threadsand.iterate Members messages, whose pages stay capped at Fluxer's 400-page ceiling. The default stays finite, so a server that keeps returning short pages still ends with.iterate Search page. An explicitLimit maxis used as given, including a value above or below the new default, except thatPages messagesnever reads more than 400 pages. Code that relied on the old default to stop a long scan early can set.iterate Search maxPages: 100 Behavior change: The
directandMessages .open directmethods no longer clear every cached private channel when they start. A successful opening replaces only the cached snapshot of the channel Fluxer returns, because opening a conversation changes no other channel. The overlap rules change in detail. A read that started before the opening is still not stored. A read of another private channel that overlaps the opening can now stay cached, because the opening no longer clears the cache when it finishes. A conflict involving the returned channel evicts that channel, and a stale successful opening evicts only its own channel. A failed opening that was started still clears the direct-message cache, because its outcome may be unknown and no channel is known to evict. Invalid input, such asMessages .send open(, fails before the cache is touched and leaves it intact. Code that relied on the full clear can call"bad") clientafter the write.cache .clear( "direct Messages") The
Paginationhint for reasonError pagenow tells callers to setLimit maxexplicitly or lowerPages max, and to narrow the filters of a message search, which reads at most 400 pages. It no longer suggests a largerItems page, because the defaultSize maxshrinks asPages pagegrowsSize -
Three additions to the shared
errorsobject and thetexthelper, plus two fixes.The new
errorsfunction returns whether a failed request may have reached Fluxer, as.outcome( error) "not,Dispatched" "rejected"or"unknown". It reads theoutcomeof any SDK error that has one and, for an EffectCause, the least certain outcome of its failures. It returnsundefinedwhen the SDK cannot establish an outcome, such as forCancelled,Error Sdk, non-SDK values and aDefect Causewith a defect, an interruption or a failure without an outcome, so the caller applies its own policy. Only"notshows that a write was not sent, so treat every other value, includingDispatched" undefined, as a write that may have applied and reconcile before sending it again. ACausecovers only the failures it kept, and Effect's concurrentforandEach alldrop the reasons of siblings they interrupt, so for several concurrent writes check each item's own result, for example with{ mode: "result" }. Wrapping an SDK call inEffectyields only a.timeout Timeout, so use the operation'sError timeoutoption to keep the outcome.Ms The new
errorsfunction is true for an SDK error with HTTP status 404, including a 404 that.is Not Found( error, resource?) messagesreports with reason.send rejected. Pass aNotsuch asFound Resource "member"or"channel"to also require Fluxer's matchingUNKNOWN_*code. A 404 says only that the resource is missing, not why: Fluxer answersUNKNOWN_for a missing channel and also for message and reaction requests in a thread from a bot that is no longer in the community, while other requests from such a bot get a 403CHANNEL missing. A 404 without a recognized code is true without a resource and false with one. Any other resource name throwsPermissions Helperwith operationError errorsand the new reason.is Not Found resource. TheNottype is exported from both entry points.Found Resource The new
textfunction shortens text to at most.truncate( content, { max Length, suffix? }) maxUTF-16 units and ends it withLength suffix(default"…") when anything was cut. It never splits a surrogate pair and does not trim or look for word boundaries. Invalid text or options throwHelperwith operationError text. The.truncate Texttype is exported from both entry points.Truncate Options ClientgainsClosed Error outcome, an addition. A REST call ended by a closing client now reports how far it got:notwhen the client was already closed or the request was still queued,Dispatched rejectedwhen closure came while waiting out a 429 before a retry, andunknownwhen the request was in flight. The outcome is also recorded indetails, and an.outcome unknownoutcome adds a hint to check whether Fluxer applied the change before repeating it with a new client. Closures that are not a REST request, such as gateway commands, collectors, waits, counts, member chunks, downloads and connecting, keepoutcomeasundefined. The preparation steps of a message send, namely opening a direct message and uploading attachments, count as not sent for the message, so a closure during them reportsnotfor the message request even if the earlier step took effect. A closure that cuts off a read recordsDispatched detailsand gets no hint to check for an applied change. The POST reads.read messages,.search membersand.search attachmentscount as reads in a closure and in their other failures, so their network and timeout failures are now retryable by.refresh Urls errors. An OAuth request that closure cuts off in flight still fails with.is Retryable OAuthreasonError networkand outcomeunknown, and a closure during OAuth instance discovery now reportsnot. A message cleanup closed while a batch waits out a rate limit now reports that batch'sDispatched rejectedoutcome instead ofunknown, and a closure between batches already reportednot. The constructor takes an optionalDispatched { outcome, read }object, so existingnew Clientcalls are unchanged.Closed Error() Partitioned
invitehandlers now order events per community. They previously ignored the invite's nestedCreate guildand.id channel, so every invite shared one key and ran one at a time. With.id partition: "guild", invites of different communities run side by side while one community's invites stay ordered, and a group invite without a community is keyed by its channel.The embed input documentation now states Fluxer's limit of 6,000 characters of combined embed text per message. The SDK does not check it locally, and exceeding it fails remotely with reason
rejectedand aninvalidAPI error.Form Body -
Add
messagesto both APIs. It answers whether one user currently has a given reaction on a message, with one request and no cache, so bots with reaction roles, verification or voting can recheck a reaction after being offline. A deleted or unavailable message fails instead of answering false.fetch Has Reaction -
Fields that Fluxer documents on gateway events and the SDK used to drop now reach handlers, in both APIs
A
messageevent carriesCreate mentioned,Here channelandType nicknames, and themessageoption accepts them. They are set only on a created message, soFields messageevents, REST reads and the message cache never carry them, and a cached message never mixes them with a later update. Fluxer sends the here mention only asUpdate true. The channel type is Fluxer's number, kept as received, with 1 for a direct message and 3 for a group direct message. The nicknames of a group direct message are a frozen object keyed by user ID, likeDirect. A value of the wrong type rejects the created message, likeMessage Channel .nicknames mentionedEveryone A
messageevent carriesDelete member, a new frozenDeletedthat is aMessage Member GuildwithoutMember usernameandis, because Fluxer removes the member's user. It is left off when Fluxer sends none, when the deletion names no community or author, and when it cannot be read, which never costs the event and logs nothing because no cache is involved. The member cache does not store itBot Voice states from
voice,State Update voiceand the call events carryState Snapshot isandE2ee Capable is, which readPeer To Peer falsewhen omitted, andversion, which is absent when Fluxer sends none. Behavior change: A voice state whosee2ee_orcapable p2pis not a boolean, or whoseversionis not a non-negative integer, is now rejected like the other malformed voice state fieldsA
guildevent carriesCreate member,Count onlineandCount joinedas Fluxer reported them when it delivered the event. They are optional because the SDK keeps accepting a body without them, and a value of the wrong type rejects the event. The guild cache does not store them, soAt guildsremains the way to read current totals.fetch Counts The testing entry points'
Wireaccepts optionalMessage channel_,type nicksandmention_, sohere fixturesemits a created message in full.message( { channel_ type: 3 }) -
GuildletsFeature Toggles .Visits Enabled guildskeep community visits on. Fluxer now enables.edit VISITS_for new communities and removes every toggleable feature that aENABLED featurelist leaves out, so an edit without it turns visits off and ends current visits. Before this, the SDK rejectedToggles VISITS_locally, so everyENABLED featureedit turned visits offToggles -
Resolvedhas a newInstance discoveryflag, in both APIs, that reports whether the instance has community directory discovery switched on. Fluxer documents a missing flag as enabled, so it readsEnabled truewhen the instance omits it. While it isfalse, the discovery namespace'ssearch,apply,editandwithdrawfail with reasonrejected, HTTP400anddetails.provider Code DISCOVERY_, whileDISABLED fetchandStatus fetchstill answer, andCategories fetchthen reportsStatus eligibleasfalse. The value is read once when the client resolves the instance. Behavior change: A discovery document whosefeaturesis present but not a boolean is now rejected as a protocol failure, like a malformed.discovery_ enabled presigned_attachment_ uploads -
Behavior change:
messagesnow returns the task's own result when a later typing refresh fails. Before, the default API returned.keep Typing Errwith the typing failure even when the task succeeded, which lost the task's value, and the Effect API combined the refresh failure with the task'sCause. Now a refresh failure stops the refreshes without interrupting the task, and it logs one Warn record with the new coderest, whose.typing Refresh Stopped fieldsnames the channel and whose.channel Id errorholds the typing failure. A task that succeeds returns its value, and a task that fails returns only its own failure. A refresh stopped by client closure is not logged. The first typing request still has to succeed before the task starts, and its failure still fails the call. A defect in the refresh work still surfaces asSdkin the default API and in theDefect Causein the Effect APICode that relied on the
Error the combinedCauseto learn about a failed refresh should read therestrecord from the logging sink instead..typing Refresh Stopped Before, client closure during the task also returned
Client(Closed Error Errin the default API,Failin the Effect API), and now the task's own result is returned. Code that detected shutdown that way should watch the client'sstateorobserveinsteadState -
Both
Clientinterfaces have a new read-onlylastproperty: The receipt of the most recently accepted event, asReceipt Eventnumbers events, or 0 before the first. It is read live and counts every accepted event, whether or not a handler receives it. Work that is not an event, such as a timer or a command's follow-up, can record it when it starts and treat any event with a larger receipt as newer, for example to drop a result that a later edit made stale. Under a supervisor each child process numbers its own events, so values from different processes cannot be comparedContext .receipt -
The member that Fluxer attaches to community events now reaches handlers. The
memberfield ofmessageandCreate messagemessages, ofUpdate messageandReaction Add messagereactions, and ofReaction Remove typingnotices carries the author's or actor'sStart Guild, read from the community's current state when Fluxer sends the event. A message's member is completed with the author's username and bot flag. The field is absent on REST reads, direct messages, webhook messages, users who left the community,Member messageevents (Fluxer sends no username there) and batch or clear events. TheDelete messageoption now acceptsFields member, and the message cache keeps messages without it, so read it in the handlerA member that Fluxer sends in a form the SDK cannot read, including a reaction or typing member without the user's name, is left off the event and the event still arrives. This differs from voice states, which reject the whole voice state
Behavior change: With
cacheenabled, these events also store their member in the member cache, whichever.members messagethe client selects, soFields membersanswers for an author without a REST read. A member that cannot be read removes the account's cached member and logs.get gateway, because the cached copy may now be stale. An event rejected as malformed that carried a member removes the account's cached member too, or the community's cached members when the event does not identify the account, or every cached member when it does not identify the community either. The exception is that a reaction or typing member without the user's name is completed from the member already cached. The cache still requests no extra gateway dispatches, and a voice state's member is still never stored because it can lag behind role edits.member Skipped The testing entry points'
Wireaccepts an optionalMessage memberwithout its user and guild ID, sofixturesemits a decorated message in tests.message( { member }) -
clienthandlers now run up to eight messages at a time by default in both the default and Effect APIs, matching.on( "message Create", ...) runand the command router. Before, aBot createbot ran them one at a time, so one slow message handler stalled every later message, while aClient runbot did not. Handlers of other events still run one at a time unlessBot partitionis set, which also allows eight. Messages can now finish out of order, so a handler that must see them in order passes{ concurrency: 1 }for strict order or{ partition: "channel" }to keep each channel in order -
Both entry points gain helpers for Fluxer's permission overwrite rules, one call for a member's permissions in every channel, error helpers for retry waits, and an arrival number on every event
The new
permissionsmethod returns one member's permission bits in every channel of a community. Pass the bot's own ID from.fetch Channels( target, options?) clientas.users .get Self() userto see what the bot can do in each channel. It always reads fresh: The guild, the member, the roles and the channel list, once each and under one deadline. It then calculates each channel locally, the same way asId permissionsand.calculate permissions. The result is a frozen array of frozen.fetch Channelentries (Permissions { channel, permissions }), in the order Fluxer lists the channelsFluxer lists only the channels the bot can view, and no threads, so call
permissionswith a thread's ID for one. The.fetch optionsare the plainRequest, withoutOptions prefer, because no cache holds a complete channel list. Failures useCache Guildwith the new operationOperation Error permissions, and errors from the reads keep their own type. A listed channel that arrives without its permission overwrites fails as it does in.fetch Channels permissions, with reason.fetch response, outcome"unknown",detailsand.read details.channel Id The new
permissionhelper returns, as a bigint, the bits Fluxer would refuse in an overwrite change. Unless the bot has Administrator, it can allow, or stop denying, only permissions it has in the channel, and Fluxer answersBits .refused Grants( current, next, held) missingfor the rest. Pass the overwrite's currentPermissions allowanddenyascurrentand the new ones asnext, withundefinedfor no overwrite, so a removal passesundefinedasnext. Pass the bot's permission bits in that channel asheld. The helper returns0nwhenheldincludes Administrator, and invalid input throwsHelperwith operationError permissionBits .refused Grants The documentation of
channels,.create edit,reorder,setandPermission Overwrite removenow states this rule for each method. Their missing-permission hints name the channel that Fluxer checks and point to the helperPermission Overwrite The new
permissionhelper compares two overwrite lists as Fluxer does: The same target IDs and, for each, the same type, allow and deny, in any order. Fluxer copies a category's overwrite change to every child whose overwrites equal the category's, with no way to opt out, so compare the children with the category before changing it. A list that is not valid, or that names a target ID twice, throwsBits .same Overwrites( first, second) Helperwith operationError permission, because Fluxer rejects duplicate IDs on writeBits .same Overwrites The new
errorsfunction returns how many milliseconds an SDK error says to wait before a retry, read from the.retry After Ms( error) retryof any operation error orAfter Ms Rate. For aLimit Error Shardit returns the wait of the failure that stopped its shard, and for an EffectConnection Error Causethe longest wait among its failures. It returnsundefinedfor an error without a usable wait and for any other value, and never throwsThe
errorsfunction now also accepts an Effect.is Retryable( error) Cause, aserrorsdoes. It is true only when the.outcome Causehas at least one reason and every reason is a retryable failure, so a defect, an interruption or an emptyCausegivesfalseEventhas a newContext receiptfield that tells which event arrived first. The client numbers each event when it accepts it, starting at 1 and counting across all of its shards in the order events arrive. Every handler of an event sees the same number, and events decoded from one frame, such asguildand itsCreate voice, get consecutive numbers in dispatch order. Handlers of one community can run out of order because of concurrency, partitions orState Snapshot partition, so compare receipts to tell which event came first. Under a supervisor each child process numbers its own eventsReady A validation error with Fluxer's code
INVALID_now has an SDK explanation in its details. Fluxer sends this code for a URL field it does not accept, such as text that is not an http or https URL, a URL with a user name or password, or a host without a top-level domainURL_ FORMAT Auditnow documents the shape Fluxer records: A change with the keyLog Actions .Member Role Update roleswhoseoldandValue neware the member's full role ID lists, so the added roles are those inValue newthatValue oldlacks. AValue Memberentry that changes roles carries the same changeUpdate Testhas a newDisconnect Options endoption, defaultSession false, in both testing entry points. Withend, the test gateway forgets the shard's session beforeSession: true disconnectcloses the connection, so the client's Resume gets Invalid Session and the client identifies a new session. As after a real session loss, the shard'ssessionincreases, the next event's context shows the higher value, and the cache release and new-session handling run. Use it with a recoverable close code, because a fatal code still ends the client. A value that is not a boolean throwsEpoch Configuration, and the nativeError disconnectdies with itWithout
end, a disconnect resumes the same session after every recoverable close code, including 4007 (invalid sequence), because the SDK starts a new session after that code only when it closes a Resume attempt.Session Testnow says thisDisconnect Options .code -
Partitioned event handlers can now wait for a partition's own setup without holding a handler slot, and a full partitioned queue no longer lets a longer backlog take room from a shorter one. The
partitionoption ofReady clientand of a.on runevent handler (Bot Event,Handler Options Bot) takes the partition key, and in the default API a signal that aborts when the subscription stops, and returns a promise. In the Effect API it takes the key and returns an Effect that runs with the services available at registration. Before the first waiting event of a key starts, the SDK calls the hook, and the key's events stay in the queue until it settles while other keys keep starting. A hook that returns a plain value marks the key ready at once. Readiness is kept only while the key has waiting or running events, so the next burst after the key went idle calls the hook again, and hooks for different keys run at the same time, so the hook should be a cheap, idempotent check. A hook that throws, rejects or fails reports every waiting event of that key as a failure of its handler, and a later event calls the hook again. Held events count towardEvent Options max,Pending Messages maxand a draining shutdown. The option needsPending Bytes partitionand otherwise fails withConfigurationwhoseError fieldis"partition, a new member of theReady" ConfigurationunionError .field Dropped events are also counted per event name.
Clienthas the newCounters eventsmember, which counts overflow, closed and collector drops and malformed dispatches whose type names exactly one event, and theDropped By Event eventsreason totals are unchanged. Drops that have no known event name appear only in the reason totals, so the per-event sum can be lower. The metricDropped fluxerly_gains anevents_ dropped_ total eventattribute where the event name is known, next toreasonBehaviour changes to know about:
- A partitioned handler with
overflow: "drop, which is the default forOldest" clientand.on run, now drops from the longest backlog instead of the oldest waiting event overall. The backlog of the arriving event's key counts the arriving event. When another key has a longer backlog, that key loses its oldest waiting event, with ties going to the key whose oldest waiting event is oldest. Otherwise the arriving event's own key loses its oldest waiting event, or the arriving event itself when that key has none waiting, and this repeats until the arriving event fits. When the arriving event would be discarded anyway, it is the only event discarded. UnderBot maxthe choice follows the number of waiting events, not their sizes. The Warn record, counters and policy field are the same, but a handler that relied on the globally oldest event being dropped, or on every partition losing events in arrival order, sees different events dropped. PartitionedPending Bytes commandsdelivery follows the same rule. Handlers without a partition and the policiesdropandNewest stopare unchanged - The partition function now runs when an event is received, instead of when the handler first considers it, so a slow partition function runs on the gateway callback. A throwing function still reports that event's handler as failed
- A partitioned handler with
-
Command routers can now wait for a partition's own setup before its commands run. The
partitionoption works forReady routerand for the.attach commandsoption ofrunandBot create(Test Bot Bot), in both APIs, with the same rules as theCommands Options partitionoption ofReady client. Before the first waiting message of a key starts, the SDK calls the hook, and the key's messages stay in the queue without using a command slot until it settles, while other keys keep running. The option needs.on partitionand otherwise fails withConfigurationwhoseError fieldis"partition. A hook that fails reports each waiting message of that key to theReady" onoption of the commands, and those messages run no commandError Behavior change for command routers that have an
on: A failingError partitionhook, a throwingReady partitionfunction and an overflow report of the router's subscription now reach thaton, in arrival order with the command failures. Before, a throwingError partitionfunction and an overflow report went to the client-levelonor the log, so a bot that replied fromError commandsstayed silent. A router without its own.on Error onis unchangedError In the Effect API the hook returns an Effect, and its required services are added to the requirements of
runandBot create, which gain a trailing type parameter for them, andTest Bot Botgains a fifth oneCommands Options -
Permission and role helpers now cover cache-first reads, role rank, timeouts, partial overwrite changes and readable permission names, in both entry points. The
permissionsmethod takes.fetch prefer(Cache: true Permission) to use enabled caches for the guild, member, roles, channel and a thread's parent and fetch only what is missing, while the default stays a fresh read. TheFetch Options hierarchyand.highest Role( member, roles) hierarchyhelpers (.can Manage Role( { guild, actor, role, roles }) Role) check rank from held snapshots, andManagement Input membersreports.timeout State( member, { now }) "none","active"or"unknown"fromcommunication. TheDisabled Until permissionhelper (Bits .patch Overwrite( current, { allow, deny, inherit }) Overwrite) changes named permissions of an overwrite and keeps every other bit, andPatch permissionreturns Fluxer's English name for a permissionBits .label( name) Behaviour changes to know about:
- Missing-permission hints and the
requireguard denial now use Fluxer's labels instead of identifiers or split identifiers, such as "Manage community" instead of "Manage Guild" and "ManageGuild", "Use voice activity" instead of "Use Vad" and "Time out members" instead of "Moderate Members". Code or tests that match this text must change. ThePermissions detailsfield still holds.required Permissions Permissionskeys - The
requireguard now decides throughPermissions permissionswith.fetch prefer, so it shares one deadline for its reads. The command is denied only when the channel's permissions cannot be confirmed, meaning Fluxer sent a channel without its permission overwrite list, and the reply is "This command cannot confirm this channel's permissions", replacing "This command cannot check permissions in this kind of channel". Code or tests that match this text must change. Every other failure still fails the commandCache - A fetched community channel that is not a thread and comes without a permission overwrite list fails
permissionswith reason.fetch responseon both paths, because Fluxer sends the list as an empty list when there are no overwrites. The failure has outcome"unknown"as other unusable responses of a read do, recordsdetails,.read detailsand.channel Id details, and carries a hint. A cached channel without the list is fetched again. With.response Field prefer, a closing client now fails a cache lookup withCache ClientoutcomeClosed Error "not, as fresh reads doDispatched" - The
channelscomment now says that Fluxer requires.remove Permission Overwrite Manage, and a bot without Administrator can remove only an overwrite whose denied permissions it has all ofRoles
- Missing-permission hints and the
-
Handlers of an update or delete event now receive the cached value from before the change, as
previouson theEventthatContext clienthandlers,.on runhandlers and event middleware receive in both APIs, so edit and deletion logs can show what changed. TheBot message,Update messageandDelete messageevents read the message cache,Delete Bulk guildandMember Update guildthe member cache,Member Remove guildandRole Update guildthe role cache,Role Delete guild,Channel Update guild,Channel Delete threadandUpdate threadthe channel cache, andDelete guildandUpdate guildthe community cache. The snapshot is the frozen value the client's own cache held just before it applied the event, never fetched. ADelete messagesnapshot is a frozen list of the messages the cache held, once each in the order of the event'sDelete Bulk ids, because Fluxer sends only IDs for a bulk deletion. It isundefinedwhen that cache category is not enabled, the cache never saw the entity, or the entry expired or was removed, and for every other event. A write through an SDK resource method updates the cache too, so the event after it can hold the written value, orundefinedwhere the write removed the entry, instead of the value from before the write. A queued event keeps its snapshot in memory until its handler runs or the event is dropped, andmaxdoes not count it. The test clients deliver it the same way when their cache option is enabledPending Bytes The new
Previousinterface lists the snapshot type per event, and the newSnapshots Previoustype gives the type ofSnapshot<K, M> previousfor one event.Eventnow takes optional type parameters for the event and message type, andContext Event,Invocation Of .context Botand theEvent Context .context clienthandler's context are typed for their event. A helper that takes a plain.on Eventstill accepts any handler's context, including one from a client withContext message, and a context built by hand, such as in a unit test, can leaveFields previousout -
The
ratelimitand.wait ratelimitlog records and the.deadline rateobservation now repeat what aLimit 429response sent about the limiter that answered it. Log records carry these values infields, andRatehas them as optional members:Limit Observation scope,bucket,limit,remainingandresetfrom theAfter Seconds X-Rate,Limit-Scope X-Rate,Limit-Bucket X-Rate,Limit-Limit X-RateandLimit-Remaining X-Rateheaders,Limit-Reset-After retryfrom a numericAfter Header Seconds Retry-Afterheader, andretryandAfter Seconds bodyfrom the body'sGlobal retry_andafter globalmembers. Each value is present only when the response sent it in the expected form. Aratelimitrecord for a request refused before sending, behind a known pause, has none. Waits, retries and the existing.deadline globalvalue are unchanged -
Roles can have icons.
Guildnow hasRole icon, the image's hash, next tounicode, andEmoji assetsbuilds the image's URL..role Icon RoleandCreate RoletakeEdit icon, an image data URI, andunicode, where null removes the icon and setting one replaces the other.Emoji Rolealso takesCreate iconto copy another role's icon. The input fails locally when it sets an image together with an emoji or a role to copy from, and the everyone role accepts neither fieldSource Role Id -
Behavior change:
text,.truncate textand the pages of.split routernow cut at a grapheme boundary, as.help Intldefines it, so a visible character stays whole. Before, they kept the two UTF-16 units of a code point together but could cut inside a character made of several code points, such as a flag, a skin-tone emoji, a joined emoji, a base letter with combining marks, or the pair "\r\n". For example,.Segmenter textreturned.truncate( "aaaaaaaaa🇩🇪", { max Length: 11, suffix: "" }) "aaaaaaaaa🇩", half a flag, and now returns"aaaaaaaaa". A piece or truncated result can now be shorter thanmaxand never longer, and the newline and whitespace preferences ofLength textonly choose positions between characters.split textkeeps whole characters only. When the first character does not fit before the suffix, it returns the suffix alone, so.truncate textnow returns.truncate( "🇩🇪x", { max Length: 4 }) "…"instead of half a flag, and a letter with more combining marks than fit is dropped entirely.textstill cuts a single character that is longer than the limit between code points, because it must advance, and it still throws.split Helperwhen the limit cannot hold one code point. Joining the pieces ofError textstill reconstructs the input exactly, except that each lone surrogate becomes U+FFFD.split No code change is needed unless a caller relied on a cut inside a character, or on a result of exactly
maxunits of textLength - suffix .length -
The
textand.truncate texthelpers now replace each lone surrogate with U+FFFD (�) instead of throwing.split Helper. Ordinary code can create one, for exampleError slicecan cut an emoji in half, and Fluxer refuses a message that contains one, so a bot's text helper no longer fails on it. The replacement has the same length, somaxstill holds, andLength textpieces join to.split content. A code path that relied on the error can check.to Well Formed() contentfirst. A suffix that is not well-formed still throws, and so does text that is not a string.is Well Formed()
Patch Changes
-
The shipped agent guide in
agents/now maps common bot tasks, such as sending messages, moderating members, checking permissions and testing, to the declaration file and members that cover them, instead of pointing agents at the re-export fileAGENTS.md dist/. It sends agents who need the website to its task index and method-by-task list first, and explains how to tell whether those pages match the installed version. The sectionindex .d.ts fluxerly agentswrites names the installed package's manifest as the place to compare versions and mentions the task map -
API comments now state several limits precisely. By default a client runs four REST or upload requests per local shard, up to 64, and four attachment downloads, and the
restand.concurrency restoptions change these numbers. Effect API users, direct messages, webhooks and application calls use the client's.media Concurrency rest, not a fixed 30 seconds. The.default Timeout Ms usersand.fetch Self directcomments explain when a result is not cached, and theMessages .open usersand.fetch directcomments state the 256-ID exceptionMessages .fetch -
A native
on,messagesor.collect messagesregistered in an already closed scope closes at once again. With Effect 4.0.6 or newer, the subscription or collector stayed open and client shutdown waited for it forever.collect Reactions -
Contain accidentally returned Promise and thenable rejections in the default API's Effect logger adapter and in structured logging callbacks, without changing SDK operation outcomes or adding asynchronous delivery or flushing guarantees
-
The
messagescomments now describe Fluxer's wider deletion. Called with a channel ID, it also deletes the bot's messages in every thread under that channel, active or archived, and for a forum or media channel it deletes the bot's messages in its posts. Called with a thread ID, it still clears only that thread.delete Own Messages -
The missing-permission hint for
messagesnow says that only a new reaction with a custom emoji from another community needs Use external emoji, matching Fluxer, which no longer checks that permission for the community's own emoji.add Reaction -
Member avatar and banner inputs and group direct message icons now accept an image data URI with parameters such as
;charset=utf-8, as community icons already did. Before, the SDK rejected them locally although Fluxer accepts them -
The
.envfile thatfluxerly initcreates for the bot token is now readable only by its owner on Linux andmac, with mode 0600, so other local accounts cannot read the token. An existingOS .envkeeps its content and permissions -
Request text with a lone surrogate, half of a character such as an emoji left by
slice, now fails locally with reasoninputand a message that explains the fix. Before, Fluxer answered it with an HTTP503error page and stored nothing, which looked like an outage. This covers every request the SDK encodes, such as message content, embeds and channel topics. Thetextand.truncate texthelpers and.split toclean such text, andWell Formed() clientstill sends its body as given.rest .request -
The
memberscomments now explain a refusal Fluxer added. Moving a member into an occupied voice channel whose participants all use peer-to-peer voice leaves the member in its current channel and fails with reason.move rejected, HTTP400anddetails.provider Code USER_, even though the member is in voice. A retry fails the same way while the destination stays in peer-to-peer modeNOT_ IN_ VOICE -
The Effect API
supervisorhelper now succeeds when the parent stops the child before its gateway session is ready, as the default API helper already did. It previously failed with.child .run Clientafter that normal stopClosed Error -
Releases now come in two kinds, Nightly and Stable, and the README's install advice says so. Every Stable ships the same code as a published nightly.
-
A nightly is an automatic build of the latest source, published under the
nightlynpm tag twice a day when the SDK changed, with a version such as1000.0.0-nightly.20261019.57, which carries the UTC build date and the run number. Run numbers only grow but can skip values, while the core of a nightly version can drop when a pending major or minor note is removed. A nightly can include breaking changes, so install it with--save-exact, or--exactwith Bun. -
The
restWarn record logged for a refused permission overwrite write now carries a precise.rejected fields. Before, a 403.hint MISSING_on setting or removing a channel's permission overwrite logged the general missing-permission hint. Now the hint is the one thatPERMISSIONS channelsand.set Permission Overwrite channelsalready put on their errors: It names Manage roles, where the channel's permission overrides title it "Manage permissions", the rule that the bot can allow, or stop denying, only permissions it has in the channel (for a removal, that it needs every permission the overwrite denies), and the.remove Permission Overwrite permissionhelper. No code change is neededBits .refused Grants -
Fixes for the guild cache, command replies, retry classification and message cleanup. A successful
discovery,.apply discoveryor.edit guildscall no longer stores its reply as a cached guild, so.edit Vanity Url guildsand guild enumeration no longer return an application or vanity object after one of these writes. These writes now only remove the cached guild.get The
Messagefrom aCleanup Error previewwhosefilterthrew now keeps the thrown value ascause, as documentedWith
mention, help pages and the usage line of a rejection reply now put one space between the bot mention and the command name, such asPrefix <@99> say <text...>instead of<@99>say <text...>. A cooldown reply now gives a wait over 60 seconds in minutes, hours or days, with at most two units rounded up, such as "Try again in 1 hour 1 minute" or "Try again in 1 day 1 hour", instead of a count of secondsThe
errorsfunction now returns.is Retryable falsefor aConnectionwith reasonError networkand an HTTP 4xx status, such as a discovery 404, matching the SDK's own retries. A network failure without a status or with a 5xx status stays retryableThe default API
clientnow throws.observe State Configurationfor a listener that is not a function, instead of logging an observer failure on every state changeError The
Commandcomments no longer giveArgument Metadata user, which registration rejects, as a type example, and say that a custom argument can also userest -
A session saved by
shardingis now resumed at most once. Before, a restart that resumed a saved session and then crashed left the old snapshot in the store, so a restart within 60 seconds of the original shutdown could resume it again and receive the same dispatches a second time, including any dispatch that caused the crash. Before resuming, startup now saves a copy of the loaded snapshot with an empty.sessions session. A later load of that copy starts a new session and logsId lifecycleat Warn, until the next shutdown saves a resumable session again. Custom stores receive this extra.session Snapshot Ignored savecall at startup. If it fails, the shard logslifecycleand starts a new session instead of resuming.session Save Failed Durations in log messages no longer read "1 min 60 s" or "60.0 s" near a minute boundary, or "1000 ms" just below a second. They now read "2 min 0 s", "1 min 0 s" and "1.0 s"
A command reply for a rejected
durationargument now shows its bounds in the argument's own syntax, such as "from 1s to 1h30m", instead of in milliseconds such as "from 1000 ms to 5400000 ms" -
An explicit
clientof a connected client now logs a failed cleanup step, such as a socket close that throws or an event handler or collector callback whose cancellation cleanup fails, at Error in the lifecycle category and counts it in.shutdown() diagnostics(). Its final record previously said the shutdown completed and the counter stayed at 0.counters .cleanup Failures -
When
clientcancels a running message or reaction collector callback whose cleanup fails, that failure now reaches the shutdown outcome, as an event handler's cleanup failure already did. It previously appeared only in the collector's own result.shutdown() -
The package's documentation links, the agent guide and the
initcommand's guide link now point to the website athttps://.fluxerly .neontechspace .com -
The comments on
GuildandBan .moderator Id Auditnow state that the IDLog Entry .user Id "0"is the Fluxer system user. Fluxer records a member removal, ban or ban removal made by its staff under that ID, and a staff ban stores it as the moderator. An invite pause that Fluxer places, lifts or lets expire is also recorded under"0"as aGuildentry. Both IDs already decoded, so no behavior changesUpdate -
With the default
childofOutput prefix, a supervisor now reads each child record and prints it in the format of the stream it goes to. SDK records print as readable labeled lines on a pretty stream, and as JSON Lines with afieldsentry on a.child jsonstream. For this, default-API children always logjson, as do Effect API children whoseloggingis.format json. SettingFLUXERLY_orLOG_ FORMAT FLUXERLY_in the child environment still takes precedenceLOG_ COLOR Before, starting a bot with a terminal on standard output and standard error redirected, such as
node supervisor, gave children readable colored lines. Their Warn and Error records then reached the parent's JSON standard error as.js 2> errors .log supervisortext, without their level, code and fields, and without the documented.child Output fieldsentry. Those records now keep their shape and gain.child fields, while standard output still shows readable lines.child -
The supervisor no longer fails with a
Supervisorof reasonError spawnwhen it stops a child while the supervisor keeps running, such as for a reshard or an unconfirmed Identify permission, and a message to that child fails because the child is exiting on its own at that moment. The supervisor waits for that exit, or force-terminates the child at theshutdowndeadline, and then handles the exit as usual, in both APIsTimeout Ms -
The supervisor
waitmethod now returns success after a normal shutdown even when a child's stop request fails because the child is exiting on its own at that moment. It previously returned aFor Close Supervisorof reasonError spawn. The shutdown completes once the child exits, or at theshutdowndeadline for a child that is still running, in both APIsTimeout Ms -
The test kit now normalizes message content as Fluxer does. Fluxer removes form feed (U+000C) and U+202E from sent message text and trims surrounding whitespace before it stores and delivers the message, so a user who types
!pingreaches the bot as!ping. Before,saydelivered the content unchanged, so a test could ignore input that a live bot answers, or the other way round. Nowsaydelivers the normalized content, and the automatic reply to an unregistered message send or edit echoes the normalized content too. A test that expected a bot to ignore content with surrounding whitespace throughsaynow sees the bot answer it, as it would on Fluxer. Deliveringfixturesthrough.message emitstill passes content unchanged