Channel, Guild, Message, SendOptions, UserOperationOptions, Count, Bot, MessageSearchOptions, Attachment, OAuth, Instance
Changelog
What changed in each release of the Fluxerly.js SDK, newest first
1000.0.0
Major Changes
-
5e98527: 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 anSdkDefectlists the original error as aDefectreason 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, socatchTagno longer recovers from it. The original error is still the public one, such asMessageorOperation Error ConnectionError, never an internal REST or gateway failureMigration: Update Effect to 4.1.0 or a later Effect 4 release. Code that read the
Failurereason of anSdkDefectto find the original error should also look at itsDefectreasons -
2c9dfde: Breaking change for code that builds
ShardStateobjects by hand, such as test doubles ofclient.shards: The newsessionEpochfield is required. AddsessionEpoch: 0to such objects. The same applies to code that buildsEventContextobjects by hand, such as tests that call handlers or middleware directly, which must now setsessionEpochandreceipt, and to code that buildsBotEventContextobjects by hand to callrunBothandlers directly, which must now setcontext -
321d8bb: Breaking changes in
commands.parseQuoted: A single quote is now an apostrophe unless it starts a word, and its rejections carry the commandname. 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
commands.parseQuotedreturns for an unclosed quote and a trailing backslash now also carryname, 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 acommands.parseQuotedrejection 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'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. -
0209093: Public type names no longer carry
DefaultorNativeprefixes, and the per-resource request option types becomeRequestOptionsandAuditedRequest 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 RequestOptionshassignalandtimeoutMs, and in@neontechspace/it hasfluxerly/ effect timeoutMs.AuditedaddsRequest Options auditReasonin both. Notes that the removed option types carried, such as the per-page deadline ofmessages, 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 Operation Options Application Operation Options Refresh Options Operation Options Resolve Options - New name
- Both
- Old name
Channel,Audit Operation Options Guild,Audit Operation Options Message,Audit Operation Options ModerationOptions,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 DefaultRestRequest,Default,Prefix Command Context DefaultSupervisor,DefaultGuardsorDefaultCommands - New name
<Name> - Effect
- Old name
Native<Name>, such asNative,Prefix Command Context NativeSupervisor,NativeGuards,NativeCommandsorNativeCommand Requirements - New name
<Name>
In the default entry point, names such as
CanManageOptions,CollectorOptions,Prefix,Commands Options SupervisorandChild Options RestRequestnow name the default shape, which includessignalwhere the oldDefaulttype did. The Effect entry point no longer exports the default-APIPrefixandCommands Options Supervisorshapes, and those names now refer to its ownChild Options -
7733b23: Make request errors say what to change. Rejections carry a
hintfor missing permissions or access, naming what a message send or a moderation action needs, unknown IDs, rejected tokens, invalid form fields, refused direct messages, two-factor requirements, slowmode, resource limits and temporary Fluxer failures, with a fallback by HTTP status for 401, 403 and 404. A rejected credential gets a hint for the credential the request sent: The webhook ID and token for a webhook client, the OAuth client ID and secret for aninvalid_clienterror or a 401 from a code exchange, refresh, revocation or introspection, and the user's access token for a 401 fromfetchIdentity,fetchGuildsorfetchConnections. Operation error messages lead with the failed operation and Fluxer's answer, for exampleMessage send failed: Fluxer reports that the bot lacks a required permission (, and explanations name Fluxer instead of "the provider"MISSING_ PERMISSIONS, HTTP 403) Keep Fluxer error codes that this SDK version does not describe yet. An uppercase code of up to 64 characters is recorded as
details.providerCodeand named in the message whileapiErrorstaysnull, and an unrecognized validation code stays inapiwith its field path and a generic explanation.Error .validation Errors OAuthOperationErrorandMessageCleanupErrorkeep unrecognized codes the same way. A response that does not match the expected shape names the failing field or check, such astypeorchannelMismatch, indetailsand logs a.response Field restWarn. A message cleanup that fails in a history read or batch request copies that step's.response Rejected details. A.response Field client.rest.requestroute has no expected shape, so its unusable response is only returned in the Result. Themembersand.fetch Can Manage permissions.fetchchecks keep these facts and the failed step's error ascausewhen they report a failure under their own operation name.Operationand theError Options MessageErrorandMessageCleanupErrorconstructors acceptproviderCodeandresponseField, and theOAuthOperationErrorconstructor acceptsproviderCodeLog HTTP
401and403rejections as arest.rejectedWarn with the Fluxer code and hint, even when the application handles the Result. When an event handler or command fails with the rejection and noonErrorhook receives the failure, the handler's failure record reports it instead, so one failed reply is logged once. Webhook clients and OAuth clients accept the sameloggingoption as a client, so their rejected webhook tokens and OAuth client credentials are logged the same way. A rejected OAuth user access token concerns one user and is only returned. Like other Warn records, identical repeats within one minute are printed once by default, so a rejection that keeps happening shows about once a minute with a repeat count. Other client rejections are logged at Debug, andrest.requestDebug records carry the Fluxer code infields.apiErrorA
ConfigurationErrorthrown by the defaultcreateClientorrunBotpoints at the caller's lineMigration:
Apiis nowValidation Error Detail .provider Code Api, so code that assigns it to anValidation Code | ( string & {}) ApiValidationCodemust narrow it first. Operation error message text changed, so match oncode,reasonandapiErrorrather than on messages -
abb5d3f: The coding agent guide moved from
consumer/AGENTS.mdtoagents/AGENTS.mdin the installed package. An application'sAGENTS.mdsection from an earlierfluxerly agentsrun points at the old path, so runnpx --no fluxerly agents,pnpm exec fluxerly agentsorbunx --no-install fluxerly agentsagain after updatingThe README now links the documentation's
llms.txtfor agents that read web pages, and its Effect section describes the accepted Effect 4 range instead of an exact version -
abb5d3f: Add
Channel(5),Type .Announcement Guild,Announcement Channel GuildTextChannelBaseandAnnouncementto both APIs. Announcement channels now decode into their own shape instead ofChannel Create GuildUnknownChannel, so exhaustive channel-type switches need an announcement caseAllow
ChannelEdit.typeto convert between text (0) and announcement (5), including a type-only patch. A text channel that follows an announcement channel cannot become one. Delete its follower webhooks first. Conversion to text queues asynchronous follower webhook removal, and aguildChannelUpdateevent replaces the cached channel with its new type -
66e4f2b: Audit-log reads and
guildevents no longer fail on an audit action this SDK version does not name.Audit Log Entry Create Auditis now aLog Entry .action Type numberthat keeps any action Fluxer records, including ones added later, so compare it withAuditLogActionsvalues. Previously one such entry rejected its whole audit-log page and made the gateway event malformed.AuditLogActionsgainsThreadCreate(110),ThreadUpdate(111) andThreadDelete(112), which also work asactionTypefilters -
7733b23: Ban durations are milliseconds, like member timeouts, command cooldowns and the
durationcommand argument. TheBanInputfields are nowdurationMs, 0 for a permanent ban or 60,000 through 63,072,000,000 for a temporary one, anddeleteMessagesMs, 0 through 604,800,000. Both must be whole seconds and are sent to Fluxer in seconds. A rejected ban input names the failing field, such asinput.durationMs, and states its whole-second rangeAudit-log entries report the message deletion of a member ban in milliseconds too, so the
AuditLogOptionsfielddeleteMessageSecondsis nowdeleteMessagesMs, in both API styles and inguildeventsAudit Log Entry Create A
durationcommand argument acceptswholeSeconds: true, which rejects a value with a millisecond part such as1m500msand adds "in whole seconds" to the rejection reply. WithwholeSeconds: true,min: 60_000andmax: 63_072_000_000, the argument's value can be passed todurationMsunchanged, and a moderator's out-of-range value gets the usage reply instead of a failed banMigration:
- Replace
durationSeconds: nwithdurationMs: n * 1000anddeletewithMessage Seconds: n deletein ban inputMessages Ms: n * 1000 - Replace
optionswith.delete Message Seconds optionson audit-log entries, and divide by 1000 where seconds are still needed.delete Messages Ms
- Replace
-
7733b23: The command argument types that pick from a fixed list are renamed to
userChoice,channelChoiceandroleChoice, next tochoice, so the namesuser,channelandroleno longer suggest a lookup in the message's community. They still select one of up to 100candidatesgiven at registration. The old names now fail registration withConfigurationError, whose hint names{ type: "id", mention }for any ID or mention,{ type: "member" }for a member of the message's community when the old type wasuser, and the new list formMigration: Replace
type: "user","channel"and"role"with"userChoice","channelChoice"and"roleChoice"in argument schemas, and in code that readsCommandArgument Metadata .type -
07d2b9d: A prefix command parser can now explain why it declined a message. The
commands.parseQuotedparser now returns a newPrefixwith a fixedCommand Parse Rejection reasonfor a quote that never closes or a backslash at the end, where it used to returnundefinedand give no hint, and a customparsefunction can return the same{ reason }shape. The router'sonUnmatchedcallback, which already handles unknown names and groups typed without a subcommand, receives the reason onCommandin both entry points, and the DebugParser Rejected commands.unmatchedrecord includes it. The router still sends no reply by itself, so a bot decides whether to turn the reason into one. A rejection may also carrynamein command-name syntax. A rejection with an empty or non-textreason, an invalidnameor other keys is reported like any malformed parser result. The commands guide now explains how to opt in to the quoted parser and how to reply to unmatched messages. A direct caller ofcommands.parseQuotedthat tests the result for truthiness or readsargsmust now rule out a rejection first, such as with"reason" in result, because only a rejection hasreasonand a parse hasname,argsandrawArgs -
7733b23: Rename client members so each name states what it does and each concept uses one word across both entry points. Deleting a bot's own history now requires an explicit
{ confirm: true }option, checked by TypeScript and again at runtime, and a missing confirmation fails with reasoninputbefore any request. Subscriptions, collectors and state observers all end withclose(), and a collector'sresult()returns its final collection.MessageErrorreportsoutcomewith the samenotDispatched,rejectedandunknownvalues as other operation errors, so an API rejection is now distinguishable from a send that never startedMigration:
- Replace
guildswith.delete Mine( guild Id) guilds, and.delete Own Messages( guild Id, { confirm: true }) messageswith.delete Mine( channel Id) messages. The required.delete Own Messages( channel Id, { confirm: true }) confirmoption joins the existing timeout and signal options. The operation IDs change fromguilds.deleteMineanddeleteMinetoguildsand.delete Own Messages deleteOwnMessages - Call
members.ban,members.unbanandmembers.fetchBansinstead ofguilds.ban,guilds.unbanandguilds.fetchBans, with the same parameter order. Their operation IDs change fromguilds.ban,guilds.unbanandguilds.fetchBanstomembers.ban,members.unbanandmembers.fetchBans - Rename
invites.fetchChannelandinvites.fetchGuildtoinvitesand.fetch For Channel invites, and.fetch For Guild webhooksand.fetch Channel webhooks.fetchGuildtowebhooksand.fetch For Channel webhooks. Their operation IDs change to the same new names, so code matching.fetch For Guild error.operationoninvites.fetchChannel,invites.fetchGuild,webhooksor.fetch Channel webhooks.fetchGuildneeds updating - Replace
applicationwith.fetch Current() application.fetch(). The operation ID changes fromapplicationto.fetch Current application.fetch - Replace
memberswith.fetch Hierarchy Check( target) members. The operation ID changes from.fetch Can Manage( target) membersto.fetch Hierarchy Check members.fetch Can Manage - Replace
memberswith.set Mute( target, true) members, and.set Mute( target, { muted: true }) memberswith.set Deaf( target, true) members. The exported.set Deaf( target, { deafened: true }) VoiceMuteInputandVoiceDeafenInputtypes describe these inputs - Call
close()instead ofsubscriptionon subscriptions, and instead of.unsubscribe() collector.stop()on message and reaction collectors, in both entry points - Call a collector's
result()instead of itswaitForClose(). The operation IDs change fromcollectorand.wait For Close reactiontoCollector .wait For Close collector.resultandreactionCollector .result - The default
clientreturns a.observe State( listener) StateObserverinstead of a function. Callobserver.close()where the returned function was called - A malformed target passed to
client.messages.getthrowsMessagewith operationOperation Error messages.getinstead ofget, matchingusers.getandchannels.get, and anSdkDefectfrom that lookup also namesmessages.get. Code that compared a lookup failure'soperationwith"get"compares it with"messages.get"instead - Replace
client.events(event)withclient, which no longer shares a name with the.subscribe( event) runBotevents option. The operation ID changes fromeventstosubscribe - Read
MessageError.outcomeinstead ofMessage. ReplaceError .delivery "notSent"checks withoutcome !== "unknown", or distinguish"notDispatched"from"rejected", and passoutcomeinstead ofdeliverywhen constructing the error
- Replace
-
6eaecbf: Expand received message context with immutable partial-user metadata, non-notifying referenced users, explicit-emoji classifications and one resolved reply level without hidden requests. The
referencedMessagetype is nowReferencedMessage, not an address-onlyMessageReference. Hand-authored message fixtures must supply the resolved message shape or omit the optional field. Address-only operations can continue to useMessageReference. Additional retained context counts toward configured cache and collector byte budgetsAdd explicit attachment URL refresh and endpoint-specific audit reasons through both APIs. Refresh preserves exact signed strings and does not download attachments. OAuth preflight now rejects overlong or noncanonical opaque values rather than changing them and bounds supplied scope arrays to 256 entries before deduplication. Member-role replacement enforces provider signed-identifier bounds
Improve resource-aware refill pacing, unrelated user and DM cache reads, incremental cache byte accounting and concurrent member-search preflight without replaying ambiguous writes. Scope logical scheduling to the owning Effect clock while retaining separate host-safety watchdogs
Native owner-backed gateway, REST, collector, member-stream, cache and presence timing now follows the Clock supplied when the client is created. Applications overriding logical time must create the client under that Clock rather than replacing it only around a later operation. Ownerless utilities retain their documented executing-clock behavior
-
7733b23: The
EmbedBuilder.fieldmethod takes its display setting as a named option, following the rule that settings are option objects rather than boolean positionals. The newEmbedFieldOptionstype describes itMigration: Replace
field(withname, value, true) field(, and drop aname, value, { inline: true }) falsethird argument. A leftover boolean or other non-object third argument throwsConfigurationErrorwith a hint naming{ inline: true } -
c8c87b0:
Embed.providerandEmbedChild.providernow have their ownEmbedProvidertype, with onlynameandurl, matching what Fluxer sends. They were typed asEmbedAuthor, whoseiconUrlandproxyIconUrlnever arrived on a provider.EmbedProvideris exported from both entry points, and a provider's icon fields are no longer decoded -
7733b23: Base every SDK error on the exported
FluxerlyErrorwith a stablecode, an optional fixhint, frozendetails, a standardcauseandtoJSON(). Errors now keep the application or transport failure that caused them. A network failure is kept as a cause that records only the transport error code, such asECONNRESET, never the transport message. API validation failures include the sanitized field path with each error code. Messages andtoJSON()output mask credential patterns, including application text quoted byApplicationErrorandSdkDefect, whilecausekeeps the original value. Masking applies to error text,describeErroroutput and log records. It covers bare Fluxer bot tokens of the form<application_, keeping the application ID. It also covers the value after an assignment to a key ending inid>.<secret> tokenorsecret, such asFLUXER_BOT_TOKEN=, the value after a quoted key such as"botToken":, and a credential-shaped value after an unquoted key and a colon, so prose such asMissing token: check the configurationstays readableAdd
describeError(error)for readable output with the code, hint, safe details and cause chain. It collapses stack frames inside the SDK and Effect into one line and prints no frames for expected SDK errors other thanConfigurationErrorandSdkDefect. It also accepts an Effect Cause and describes each failure, defect and interruption, so native programs can passexit.causedirectly. A thrown value that is not an Error is namedNon-Error value (and similar. Addstring) errorswithapiCode,isRetryableandmatch. Theerrorshelper returns the recognized Fluxer rejection category of an operation error, such as.api Code( error) "missingPermissions"or"twoFactorRequired", and undefined for every other value, soerrorsworks on any error of an operation's Result without checking its.api Code( result .error) === "missing Permissions" _tagfirst. A read that failed after it was sent recordsdetails.read, soerrors.isRetryabletreats it as safe to repeat, unlike a write with an unknown outcome. AddApplicationErrorfor failures raised by application callbacks such as arunBotsetuporcommandscallback or akeepTypingtaskMigration:
Message,Operation Error Channel,Operation Error GuildOperationError,UserOperationError,Webhook,Operation Error Bot,Application Operation Error Attachment,Refresh Error MessageCleanupError,MessageErrorandOAuthOperationErrornow take one options object, described byOperationfor the resource operation errors. ReplaceError Options new AttachmentwithDownload Error( reason, status, input Validation) new Attachment.Download Error( { reason, status, input Validation, cause }) CountOperationError,MemberChunkError,PaginationErrorandPresenceErrorchange the same way, taking{ operation, reason, input,Validation, cause } { reason, retry,After Ms, input Validation, cause } { operation, reason, inputandValidation, cause } { reason, input.Validation, cause } PresenceErrorno longer defaults its reason toinput, and omitted status, retry and input detail values still default to null.ConfigurationError,ConnectionError,HelperError,AssetUrlError,CollectorError,EventWaitError,SupervisorErrorandSupervisorChildErroraccept a trailing options object withcause.ConfigurationErroralso acceptshintthere, andConnectionErroracceptshintanddetails -
7733b23: Report every failure that has no returned result, including throwing event handlers, failed commands, collector callbacks and filters, cleanup progress callbacks, message cache callbacks, state observers and overflowing subscriptions. Each failure goes to the subscription's
onError, then the client's newonErroroption, or is logged at Error with its full message, stack and cause chain. AFailureReportcarries the original thrown value unchanged, its kind, and the event, command, subscription and message IDs involved, anddescribe()returns readable text. Native reports also keep the Effect causeA failure only queues its report. Each client, subscription and command router hook receives reports one at a time in order through its own bounded queue, so a slow hook never holds up handlers. Later reports are logged and counted instead, and a failing hook is logged together with the original failure without being retried. Shutdown interrupts running hooks without waiting for one that never finishes, and logs the interrupted and queued reports. Command routers now report failures with the command name, including failed
onUnmatchedcallbacks and parsers, and rejected or unmatched commands are logged at Debug and countedMigration:
HandlerErrorReportis replaced byFailureReport, and the message cacheonErroroption andCacheare removed in favour of the client'sPolicy Error Report onError. A message cleanuponProgresscallback that throws, rejects or returns an Err is now reported toonErrorwith kindprogressinstead of being ignored, and cleanup continues. The callback stays synchronous, so a returned Effect is not run and is reported as aTypeError.SdkDefectnow keeps each fault's original value inreasonsand its first fault ascause. A fault from an application callback, or a throw while the SDK reads caller-supplied options or input, such as a property getter or a caller AbortSignal'sabortedgetter or listener methods, has originapplicationand codeapplication.defect. Other faults, including faults in SDK work around those reads, have codesdk.defect -
7733b23: Invalid data from the Fluxer gateway, such as a frame that is not JSON, a second HELLO or READY, or a sequence that goes backwards, no longer ends the client at once. The shard logs the failure with
fields.reasonset toprotocoland reconnects with a new session. Three such failures in a row without a healthy connection between them end the shard with a protocolConnectionErroras before. Oversized frames, invalid UTF-8, binary frames andgateway: { onstill end the shard without retryingMalformed Dispatch: "terminate" } Migration: A single invalid gateway message no longer ends
client.run, because the shard reconnects. The protocolConnectionErrornow arrives only after three such failures in a row -
7733b23: The
runBotfunction lets running work finish before it stops, in both entry points. When itssignalaborts, or SIGINT or SIGTERM arrives withprocessSignals: true, the bot stops accepting new events and gives running handlers and commands, events already waiting for them and their REST requests up to 5,000 ms to finish. Then it shuts down as before and cancels whatever is left. The newdrainMsoption changes that time. A stop caused by a failure still shuts down at once. Sending the same process signal again ends the process at once with the default Node.js behaviorAdd
ShutdownOptionswithdrainMstoclientin both entry points for the same drain outside.shutdown( options) runBot. Without options,shutdown()still cancels running handlers at once. During a drain the connection state stays as it was, new subscriptions and event waits start closed, and handlers can still send requests. The drain ends as soon as no work is left, logginglifecycle.drained. When the time runs out, alifecycleWarn record names how many handlers, events and requests were cut off. A.drain Timed Out lifecycle.drainingInfo record marks the start. An invaliddrainMsthrowsConfigurationErrorin the default API and dies with it in the Effect APISupervised children drain the same way when the supervisor stops them, for up to the supervisor's
shutdownTimeoutMsminus one second, which keeps that second for closing the connection. A child whose client stopped on its own is not drainedMigration: To keep cancelling running handlers as soon as the bot is asked to stop, pass
drainMs: 0torunBot -
7733b23: Cache entries for communities, which Fluxer calls guilds, and for members, roles, emojis, stickers and channels now survive a successful Resume, because Fluxer replays every missed event on Resume or refuses the session. They are cleared when a shard has to start a new session. Users, direct messages and messages are still cleared on every lost connection
A
guildCreateevent now fills the enabled role, emoji, sticker and channel caches from its snapshot and replaces that community's earlier entries, so a new session rebuilds them without REST requests. Members are not replaced, because the snapshot lists only a few. With the member cache enabled, the ones it lists are stored beside the othersMigration: Code that relied on community caches being empty after a reconnect should call
guilds.fetchand the other fetch methods when it needs current state, since a cached entry now stays across a Resume -
7733b23: Skip a known gateway event that fails validation instead of ending the client. The SDK logs
gatewaywith the event type and failing field path, clears cache entries the event could have changed and counts it. Set.dispatch Rejected gateway: { onto end the shard with a protocolMalformed Dispatch: "terminate" } ConnectionErrorinsteadAdd an
overflowhandler option withstop,dropOldestanddropNewestpolicies. Every dropped event is logged at Warn and counted indiagnostics(). Handlers registered with.counters .events Dropped client.ondrop the oldest waiting event by default when their queue is full, asrunBothandlers and command routers do, and the handler keeps running. An unknown event name fails withConfigurationErrorwhose hint suggests the closest valid name when one is similarMigration: A full
client.onqueue no longer ends the subscription. To keep that behavior, wherewaitForClosereturnsEventOverflowError, pass{ overflow: "stop" }toclient.on. Thesubscribestreams, event waits and collectors still end on overflow. Event handlers passed torunBotnow runmessageCreateup to eight at a time and drop the oldest waiting event when their queue is full instead of stopping the bot. Pass{ handler, concurrency, overflow, maxinstead of a function to change this per event. Command routers attached without options, or with undefined settings, also run up to eight commands at a time and drop the oldest waiting messagePending Messages, max Pending Bytes, on Error } -
abb5d3f: Received
messagenow has its own type,.message Reference Message, separate from theContext Reference MessageReferencethat message operations take in both APIs. ItschannelIdis required andidis optional. Channel-follow notices, which have no message ID, now decode in events and REST reads, including minimal message field selections. A present but malformed ID still failsThis is a breaking type change: Check that the received context has an ID and build a
MessageReferencefrom both fields before passing it to fetch, reply or another message operationAdd
Messageand the received-only flagsType .Channel Follow Add Message,Flags .Crossposted MessageandFlags .Is Crosspost Message. Fluxer sets these publishing flags itself. Sending and editing continue to accept onlyFlags .Source Message Deleted SuppressEmbedsandSuppress, with other flags rejected before sending the requestNotifications -
2d263aa: Fluxerly needs Node.js 24.15 or newer, up from 24.11, because that is the oldest Node 24 release npm 12 supports
-
7733b23: Each helper has one public name, and the role hierarchy helpers join the other pure helpers in a namespace:
- The top-level
createPkceexport is removed. Useoauth.createPkce, which is unchanged - The
hierarchynamespace replacescanManageHierarchy,compareHierarchyandisAboveInHierarchywithhierarchy.canManage,hierarchy.compareandhierarchy.isAbove. TheHierarchyHelperstype describes it
Migration: Replace
createPkce()withoauth.createPkce(), and each loose hierarchy function with itshierarchymethod - The top-level
-
7733b23: Reject misspelled and unsupported top-level option keys in
createClient,runBotandsupervisor.createin both entry points withConfigurationError, whose hint names the closest supported key, such aseventsforevent. TheclientOptionsofsupervisor.child.runare checked the same way, where an unknown key was silently dropped before, so the child client ran without it. Unsupported keys in the nestedlogging,cache,cache.messages,cache.<kind>,connection,connection.recovery,uploads,gateway,sharding,instanceand supervisorrestartsettings are named in the message with the same suggestion, and a removed logging setting names its replacementNormalize the configured token: Surrounding whitespace and one pair of matching quotes, which
.envfiles often add, are removed. A token that starts with aBotorBearerscheme is rejected withConfigurationError, since the SDK adds theBotscheme itself. Theauth.rejectedhint suggests checking the configured value before regenerating the tokenMigration: Remove option keys that
createClient,runBot,supervisor.createor theclientOptionsofsupervisor.child.rundo not support, including unknown nested keys such as aconnectionkey other thanstartupTimeoutMs,maxStartupAttemptsandrecovery, and pass the bare token without aBotorBearerprefix -
abb5d3f: Remove the retired phone-verification level. The
Guildconstant is gone and guild edits reject level 4 before the request. Level 4 received from older instances decodes asVerification Levels .Very High High(3), matching Fluxer. Other out-of-range levels still fail decoding. TheApiProviderCodetype dropsGUILD_, and an unrecognized code stays inPHONE_ VERIFICATION_ REQUIRED details.providerCode -
7733b23: Return plain values from the local helpers in both entry points, and throw their existing error for invalid input, which indicates a programming mistake. This covers
format,snowflakes,permissionBits,colors,text.split,links,assets, the role hierarchy helpers inhierarchyand theassetsandlinkshelpers returned byclient. Invalid input throws the same.instance .resolve() HelperError,AssetUrlErrororGuildOperationErrorwith the sameoperationandreasonthat the Result or Effect previously carried. In the native API such a throw inside Effect code becomes a defect rather than a typed failureAdd
format,.try Parse Mention format,.try Parse Timestamp format,.try Parse Custom Emoji snowflakes.tryParseandcolors.tryParsefor text received from users. They apply the same rules as their plain counterparts without throwing, returning a Result in the default API and an Effect that fails withHelperErrorin the native APIMigration:
- In the default API, helpers such as
formatnow return plain values directly, so drop.user Mention( id) isOk(),valueand_unsafeUnwrap()handling from helper calls, and catchHelperErroronly where invalid input is possible - In the native API, helpers are no longer Effects, so drop
yield*andEffect.runPromisearound calls such asformat,.user Mention( id) links,.installation( id) assets,.display Avatar( user) hierarchyand.can Manage( input) resolved.links .channel( target) - Parse untrusted text with
format,.try Parse Mention( text) format,.try Parse Timestamp( text) format,.try Parse Custom Emoji( text) snowflakesor.try Parse( text) colorsinstead of.try Parse( input) parseMention,parseTimestamp,parseCustomEmoji,snowflakes.parseorcolors.parse, which now throw - Native code that deferred validation by building a helper Effect before its input changed now reads the input when the helper is called
- In the default API, helpers such as
-
7733b23: Make public types describe what Fluxer actually sends and what builders accept.
GuildChannelis a union ofGuildTextChannel,Guild,Announcement Channel GuildVoiceChannel,GuildCategoryChannel,Guild,Announcement Thread Channel Guild,Public Thread Channel Guild,Private Thread Channel GuildForumChannel,GuildMediaChannel,GuildLinkChannelandGuildUnknownChannel. Comparingchannel.typewith aChannelTypeconstant narrows it to exactly that shape, soif (yields achannel .type === Channel Type .Voice) GuildVoiceChannel. A channel type this SDK version does not know arrives as aGuildUnknownChannelwhosetypeis"unknown"and whoserawTypeholds the number Fluxer sent, so aswitchontypethat also handles"unknown"is exhaustive.Presenceis anUpdate .status Observed, and an unrecognized status from Fluxer becomesPresence Status "unknown"instead of an arbitrary string.EmbedBuilder.coloraccepts anyColorInput, such as"#ff8800"or an RGB tuple, andEmbedaccepts aBuilder .timestamp Date, epoch milliseconds or an ISO string. An invalid color, or an invalidDateor number, throwsHelperErrorwith operationembed.colororembed.timestamp. The type error from callingbuild()on an emptyMessageBuildernow names the missing body, through the exportedMissingMessageBodytype. Default clients, OAuth clients, webhook clients, subscriptions and collectors supportawait using, which shuts a client down or closes a subscription or collector and waits for its cleanupMigration:
- Narrow a
GuildChannelontype, for example withChannelType.Voice, before readingbitrate,userLimitor other fields that belong to one channel type - Handle channel types outside
ChannelTypeby checkingchanneland reading the Fluxer number from.type === "unknown" channel.rawType. A numerictypeno longer carries an unknown type - Compare
Presenceagainst the listed values and handleUpdate .status "unknown"instead of Fluxer strings outside that list Embedconverts its input, so pass the color directly rather than converting it first. A number outside the 24-bit range now throwsBuilder .color( value) Embedconverts aBuilder .timestamp( value) Dateor number withtoISOString(). An invalidDateor number now throws instead of producing an invalid embed
- Narrow a
-
7733b23: Public types now use one name per concept, and API rejection details are readable in the reference, in both entry points
MemberandSearch Hit .global Name OAuthare nowIdentity .global Name displayName, the nameUserandMessageUseralready use for the account-wide display name. Thedisplay.namehelper therefore reads it from member-search hits and OAuth identities too- The deprecated
MessageMentionalias is removed.MessageUseris the only name for the partial-user shape in mentions and references ApiErrorDetailandApiare plain interfaces. The newValidation Error Detail ApiErrorCode,ApiProviderCodeandApiValidationCodeunions list every recognized category and server code to branch on. Theexplanationfields are typedstring, andvalidationErrorsis present only whencodeisinvalidFormBody- The
@neontechspace/entry point no longer exports the ten default-API option typesfluxerly/ effect Default,Member Chunk Options Default,Count Operation Options Default,Bot Application Operation Options Default,Expression Delete Options Default,User Operation Options Default,Webhook Operation Options Default,Attachment Download Options Default,Attachment Refresh Options DefaultandAttachment Stream Options Default. No Effect member accepts themMessage Cleanup Options
Migration:
- Read
displayNameinstead ofglobalNameonMemberSearchHitandOAuthIdentity - Replace
MessageMentionwithMessageUser - Use
ApiErrorCode,ApiProviderCodeorApiValidationCodeto branch on codes. Code that comparedexplanationagainst literal types compares strings instead - Effect code that imported one of these types imports the default-API type from
@neontechspace/under its name without thefluxerly Defaultprefix, orRequestOptionsorAuditedfor the operation option types, or uses the Effect option type of the same operationRequest Options
-
7733b23: Return a Result or a failing Effect only where a failure can happen at runtime, such as a request, a gateway command, an event wait or a collector's outcome. Local setup returns its value directly: Creating clients, registering handlers, creating command routers and reading caches. Misuse of that setup, such as invalid options, a missing token or an unknown event name, throws
ConfigurationErrorin the default API and is a defect in the native API, so a misconfigured bot fails at startup instead of returning an easily ignored Err. An Err returned or resolved by an application callback, including event and command handlers, guards, middleware, collector callbacks,onErrorhooks, state observers and supervisor configuration, is now reported like a throw of its errorThe default API exports
orThrow, which returns a Result's value or throws its error and also accepts a Promise of a Result, and re-exports theResultandResultAsynctypes. Callback return types acceptunknown, and handler, collector and supervisor signals are typed asAbortSignal. A missing or blanktokenis accepted by the types asstring | undefinedand throwsConfigurationErrorwith a hint that the environment variable holding it is probably unsetMigration:
- Default
create,Client( options) create,Webhook Client( options) oauth.create(config)andsupervisorreturn the client or supervisor directly. Drop.create( options) isErr(),valueand_unsafeUnwrap()handling and catchConfigurationErroronly where options come from untrusted input - Native
createClient,createWebhookClient,oauth.createandsupervisor.createno longer fail withConfigurationError. Invalid options become a defect, so handlers for that typed failure can be removed - Default
clientreturns the.on( event, handler) Subscription,clientreturns the.subscribe( event) EventSubscription, andmessagesand.collect( ...) messagesreturn the collector directly. A gateway that is not ready or an aborted signal now appears in the collector's.collect Reactions( ...) result()instead of the creation result. Nativeon,collectandcollectReactionsno longer fail, and a nativesubscribestream fails only withEventOverflowError - Cache lookups such as
clientreturn the cached value or.messages .get( reference) undefineddirectly, and returnundefinedafter shutdown instead ofClientClosedError. A malformed ID throws the namespace's operation error with reasoninputin the default API and is a defect in the native API. Native lookups otherwise return an Effect that never fails - The default
clientreturns the array directly, and native.cache .entries( kind) cache.entriesnever fails - The
clientmethod returns the.permissions .calculate( input) bigintdirectly in both entry points and throwsGuildOperationErrorwith reasoninputfor inconsistent data - Command routers return values directly in both entry points. The
commands.create,register,registerMany,registerGroup,helpandcommandscalls throw.memory Cooldowns() ConfigurationErrorfor invalid definitions, and the defaultattachreturns theSubscription. Dropyield*in front of those calls in native code, whereattachremains an Effect - A default cooldown store's
claimreturns the claim or a Promise of it rather than a Result - Callbacks that returned an Err to signal an ignored outcome now report a failure. Return nothing, or handle the Result inside the callback, when the failure is expected
- The
RegistrationErrorandCollectorexports are removed, because registration misuse now throwsRegistration Error ConfigurationErrorand a registration during shutdown returns a closed handle - TypeScript consumers that read handler signals or use
await usingneedAbortSignalandAsyncDisposablefrom the Node.js types, for example"types": ["node"]
- Default
-
7733b23: Command
idarguments now accept only IDs in the 64-bit ID range, in both entry points. A valid ID is a nonzero decimal without leading zeroes, up to9223372036854775807. The same limit applies to IDs inside the accepted mention forms. The value0and longer or larger values are now rejected asInvalidbefore the command runs. Commands that relied on0or out-of-range tokens reaching the handler need acustomargument instead -
7733b23: Command cooldowns no longer turn users away because a memory store is full, and commands can be hidden from help and suggestions, in both entry points
- A memory cooldown store never refuses a new key. When it is full, it removes expired keys first, then the reservation that expires soonest, which lets that key run again early. This applies to
commandsand to each router's own store.memory Cooldowns() - The default limit rises from 1,024 to 10,000 keys, and the router option
cooldowns: { maxsizes a router's own store, for exampleEntries } commands.create( { prefix: "!", cooldowns: { max Entries: 50_000 } }) - Commands and groups accept
hidden: true. Generated help leaves them out, along with everything inside a hidden group, and selecting a hidden group inhelpthrowsConfigurationErroras for a missing one. Hidden commands still run when invoked, so guard the ones that need it
Migration:
- The
CooldownCapacityclaim and theCommandrejection are removed, because a built-in store no longer refuses a key. A custom store returns onlyCooldown Capacity CooldownAcquiredorCooldownActive, and any other claim fails the command withConfigurationError. Code that handled either removed tag can drop that branch - Code that relied on the old limit of 1,024 keys, for example to cap memory, passes
maxEntriestocommandsor the router's.memory Cooldowns cooldownsoption - Owner-only and other private commands that must not appear in help set
hidden: trueinstead of filtering them in everyhelpcall'sinclude
- A memory cooldown store never refuses a new key. When it is full, it removes expired keys first, then the reservation that expires soonest, which lets that key run again early. This applies to
-
7733b23: Registering a subscription or collector after client shutdown has begun no longer fails with
ClientClosedErrorat registration, in both entry points. A handler that is still running when shutdown starts can register, which is a runtime race rather than misuse- A
client.onorclient.subscriberegistration, including a command router'sattach, returns an already-closed subscription. Its handler never runs,waitForClosesucceeds and the defaultnextreturnsOk(null). The nativesubscribeStream ends without events. Each such registration writes a Warn log record with codeevents.registered After Shutdown - A
messages.collectormessagesregistration returns a collector whose.collect Reactions resultfails withClientClosedError, as it does for a collector already running when shutdown starts - Invalid arguments are misuse and throw or die with
ConfigurationError, whatever the client state. Registering middleware withclient.useon a closing client throws or dies withClientClosedError
Migration:
- Code that handled
ClientClosedErrorfrom these registrations reads the handle instead. Checkclient.state, or read the collector'sresultforClientClosedError
- A
-
7733b23: Configure a whole bot with one
runBotoptions object in both entry points. Acommandsoption routes prefix commands,setupruns after every handler is registered and before the connection starts, andonErrorandprocessSignalssit beside them. TherunBotfunction checks every option, including event names, event delivery settings and client settings, before any client, process signal listener or request exists, even when thesignalis already aborted. Invalid configuration other than a missing or blanktokenthrowsConfigurationErrorat the call, or is a defect in the native API. It then creates the client and registers every event and command synchronously. An abortedsignalwith valid options returns success without creating a client. A failedsetupor an application error thrown by acommandsregister callback is an application failure, reported asApplicationErrorwith the original value ascause. In the default API, asetupcallback that throws, rejects or returns an Err givessource"runBot setup", and in the native API a failedsetupEffect gives the same. A register callback that throws givessource"runBot commands"in both APIs. A failedsetupstops the bot before connecting and shuts down its client, and a failed register callback stops it before any client exists. Misuse inside the register callback isConfigurationError, and anSdkDefector a native defect keeps its original causeCommands gain features for common bot needs:
- The
guardoption accepts one guard or an array, and a guard can return{ deny: "reason" }. The exportedguardsprovideguildOnly(),dmOnly(),ownerOnly(ids)andrequirePermissions( names) - The
guildOnly()guard allows a message sent in a community, which Fluxer calls a guild, and denies every other message without a request. ThedmOnly()guard allows only a confirmed one-to-one or group conversation: It checks the direct-message cache, then the channel cache, and otherwise reads the channel once withdirectMessages.fetch, which also fills an enabled direct-message cache. A community channel is denied, and any other failed read fails the command and is reported with the command name. In the Effect API, its error type isUserOperationFailure - The
requireguard reads cached community, member, role and channel data first. It reads a cached channel that lacks its overwrite list again, and denies the command when a fetched channel that is not a thread still lacks that listPermissions( names) - The
cooldownoption accepts{ durationMs, per }withperset to"user","channel"or"guild", and uses a memory store owned by the router whenstoreis omitted - The router's
useoption adds middleware that wraps every command and can stop it by not callingnext. In both APIs,nextfails with the command's own error, and each middleware stage and the command run at most once per message, even whennextis called again or evaluated concurrently. A command failure is reported once with its command name, whatever the middleware does with it. Middleware that fails with a different error adds its own report after the command's failure - Setting
onReject: "reply"sends a short explanation, such asMissing name. Usage: !greet <name>, the guard's reason or the cooldown's remaining time. The commands ofrunBotuse it by default, while a router fromcommands.creategives no feedback unlessonRejectis set. SettingonReject: "silent", on a router or on one command, sends nothing, and the rejection is still counted and logged at Debug ascommands.rejected. An active cooldown key is answered once until its retry time, and a guard denial once per user and command every 5 seconds, while argument rejections are answered each time. A skipped reply still counts as a rejection and is logged at Debug withfeedbackSuppressed. AnonRejectfunction receives every rejection. Native replies measure the remaining time with the handler's Effect Clock - Setting
mentionPrefix: trueaccepts a mention of the bot,<@id>or<@!id>, as a prefix, and theprefixoption can be resolved asynchronously. The bot's ID comes from the user in the gateway's READY. When READY lacks it, one sharedusers.fetchSelfread supplies it, and a failed read is logged at Warn and retried after a backoff that starts at 5 seconds and doubles up to 5 minutes - An unknown command name passed to
onUnmatchedand its Debugcommands.unmatchedlog record carrysuggestion, the closest registered command or group name when one is similar. Two swapped adjacent characters count as one edit, sopnigsuggestsping. Hidden commands and everything inside a hidden group are never suggested - The bound
replyaccepts a plain string - Argument descriptors add
min,maxanddefaultfor integers and numbers, and newduration,memberandcustomtypes. Amemberargument accepts only IDs in the 64-bit ID range, asiddoes. Acustomparse function is synchronous: A returned Err fails the command like a throw, and a returned Promise or Effect is not run as the value but fails the command withConfigurationErrorfor fieldcommand, without an unhandled rejection
Migration:
- The
runform is removed. Move event handlers intoBot( options, install, run Options) events, commands intocommands, other startup work intosetup(client), andrunOptionssettings such assignalandprocessSignalsinto the options object - Code that checked
runBot's Result for configuration failures catches the thrownConfigurationErrorinstead, except for a missing or blanktoken, which stays in the Result. The Result reports connection failures, cancellation and stopped workers as before, and a failedsetuporcommandscallback asApplicationError. A handler or command router set tooverflow: "stop"that overflows is reported and stops the bot, and the run fails withCritical, whoseWorker Stopped Error causeis the subscription'sEventOverflowError. SoEventOverflowErroris no longer part of therunBoterror type in either API. MatchCriticalinstead in code that matches onWorker Stopped Error runBotfailures, or setoverflowtodropOldestordropNewestto keep the bot running Criticalcounts event subscriptions in configuration order, followed by the command routerWorker Stopped Error .worker Index - A cooldown without
storeno longer requires one. Passperinstead of building the key from the message when the default key fits - Keyed
registerManyentries may omitargumentsfor unrestricted raw args. TypeScript projects withexactomit it rather than passOptional Property Types arguments: undefined - The commands of
runBotnow answer a rejected command. SetonReject: "silent"in thecommandsoption ofrunBotto keep rejected commands unanswered. Unmatched commands, and rejected commands of a router fromcommands.create, still get no reply unlessonUnmatchedoronRejectasks for one
- The
-
dd52acd: The
runBotfunction now skipsmessageCreateandmessageUpdateevents written by bots, including the bot's own messages, before they reach itseventshandlers or itscommandsrouter, in both entry points. A reply can no longer trigger the handler that sent it, and handlers no longer need amessage.author.isBotcheck. The newignoreBotsoption controls this in one place and defaults totrue. Setting it tofalselets both the handlers and the prefix commands see bot-authored messages. Acommands.ignoreBotssetting, when present, still decides for the prefix commands alone. A value other than a boolean is misuse. ThecreateTestBotfunctions of both testing entry points accept the same optionMigration: A bridge, logging or moderation bot that handles messages from other bots passes
ignoreBots: falsetorunBot. Bots built withcreateClientandclient.onare not affected -
944c7bf: A missing or empty token no longer makes
runBotthrow in the default API or die in the Effect API. Once the other options are valid,runBotreports it like any failed run before creating a client: It logs theConfigurationErroronce without a stack trace, setsprocess.exitCodeto 1 unlessreportFailureisfalse, and returns it as an Err in the default API or fails with it in the Effect API. BothrunBotfailure types now includeConfigurationError. Other invalid options still throw in the default API and die in the Effect API, andcreateClientstill rejects a missing token as misuseThe missing-token hint now also covers projects created by
fluxerly init: SetFLUXER_BOT_TOKENin the.envfile that init wroteMigration: Code that caught the thrown
ConfigurationErrorfor a missing token around a defaultrunBotcall reads it from the returned Err instead. Effect code that handled the defect handles the typedConfigurationErrorfailure -
37612fd: A
runBothandler set tooverflow: "stop"whose queue fills now stops the bot in both entry points. The bot previously kept running without that handler, as its API comments said, so the handler's events stopped arriving while the bot looked healthy. The run now fails withCritical, whoseWorker Stopped Error detailshold theevent, the exceededlimit,messagesorbytes, and itscapacity, and whosecauseis the subscription'sEventOverflowError. The overflow itself is still reported toonErroror logged. A bot that should keep running setsoverflowtodropOldestordropNewest, which are unchanged -
dd52acd: The default API's
runBotnow handles SIGINT and SIGTERM unlessprocessSignalsisfalse. Ctrl+C or a stop from a process manager such as Docker, systemd or PM2 requests a normal stop that drains running work fordrainMs, then the listeners are removed and the process can exit. The runner still never exits the process itself, andprocessSignals: trueis no longer neededThe Effect API's
runBotkeeps process signals opt-in. A launcher such asNodeRuntime.runMainalready interrupts the program on these signals, and a second handler would race its interruption. SetprocessSignals: truethere only when nothing else handles the signalsMigration: An application that handles SIGINT or SIGTERM itself, or runs several bots in one process with its own shutdown, passes
processto the defaultSignals: false runBot -
7733b23: The
runBotfunction reports a failed run for the process by default, in both entry points. When the bot stops because of a failure, it logs that failure once as an Error record with codelifecycle.botFailedand setsprocess.exitCodeto 1. It skips the record when the client already logged the same error, such as thelifecyclerecord of a rejected token, so the failure appears once. The failure is still returned, and a normal stop or an interruption reports nothing. The native API also reports a defect this way, including misuse found before any client exists, such as a missing token or a misspelled option key. In the default API, misuse throws.connection Ended ConfigurationErrorand a defect rejects withSdkDefect, which Node.js prints before it exits with a failing code. A bot entry point therefore needs no try/catch:await runis complete in the default API, and so isBot( { ... }) await Effectin the native API.run Promise Exit( run Bot( { ... })) Migration: Code that already logs the returned failure and sets the exit code can drop that handling. To keep full control of the failure output and exit status, pass
reportFailure: false -
abb5d3f: Message search no longer fails when a result comes from an unnamed group DM, in both entry points. Fluxer now sends
name: nullfor such channels, soMessageis nowSearch Channel .name string | nullwhen present, and code that reads it must handle null -
7733b23: Forward each supervised child's output in the supervisor's console format by default, and add the
childOutputsupervisor option withprefix,inheritandignore. Readable output labels each line with its shard or child ID. JSON output stays valid JSON Lines: A child's SDK record gainsfields.child, another JSON object passes through unchanged and other text becomes asupervisorrecord. With.child Output prefix, children receive the supervisor's format and color throughFLUXERLY_LOG_FORMATandFLUXERLY_LOG_COLORunless those variables are already set. Very long lines are truncated with a marker, forwarding respects output backpressure, and exit handling waits briefly for a child's final output, such as a crash stack trace. The supervisor now logs child start, exit, crash, restart and spawn failures, and spawn errors keep their original cause. The newloggingsupervisor option accepts the client logging settings for these recordsMigration: Supervised child output was previously discarded. Set
childto keep discarding itOutput: "ignore" -
7733b23: Supervisors now restart a crashed child by default, with the documented defaults of
Supervisor: Up to 3 restarts in a row, delays from 1,000 ms doubling up to 30,000 ms, and a fresh budget after 60,000 ms of healthy running. Previously an omittedRestart Options restartoption meant that one crashed child stopped the whole supervisor and took every shard offline. Therestartoption now also acceptsfalse, and thesupervisor.crashrecord names that setting when it stops the supervisorRestart budgets now count consecutive crashes instead of every crash over a child's lifetime. A child that runs for the new
restart(default 60,000 ms) after finishing configuration gets its full budget and the first delay back at its next exit. When that restores used restarts, the.healthy Reset Ms supervisor.restartrecord hasfields.budgetResetset to true. A crash loop still exhaustsmaxAttemptsand stops the supervisorMigration:
- To keep the previous behavior, where one unexpected child exit stops the supervisor, set
restart: false - To keep a lifetime-wide budget in practice, set
restartto a period longer than the expected process lifetime, up to 2,147,483,647 ms. The.healthy Reset Ms restartscount instatus()child snapshots also starts again after such a reset
- To keep the previous behavior, where one unexpected child exit stops the supervisor, set
-
314ac29: Test clients in both testing entry points now answer message sends (
POST /) and edits (channels/:id/ messages PATCH /) that nochannels/:id/ messages/:id rest.respondregistration matches. The reply is a message by the bot account that echoes the request's content, embeds, flags and tts, so a first test needs no response fixture. A registered response still wins, and other unmatched requests still receive a 404 with atestingWarn.unmatched Request Migration: A test that relied on the 404 for an unmatched message send or edit registers that failure with
rest.respond -
314ac29: Test waits in both testing entry points, such as
TestRoute.nextandidle, no longer fail after a default 2,000 ms. WithouttimeoutMsthey wait until their condition holds, the test client shuts down or, in the Effect API, the wait is interrupted, so the test runner's own timeout ends a test that hangs. An explicittimeoutMsstill fails the wait withTestTimeoutErrorMigration: A test that expects
TestTimeoutErrorfrom a wait without options passestimeoutMs -
dd54820: Threads, forum channels and media channels now have their own channel shapes in both entry points, instead of arriving as
GuildUnknownChannel.ChannelTypegainsAnnouncementThread(10),PublicThread(11),PrivateThread(12),Forum(15) andMedia(16), andGuildChannelgainsGuild,Announcement Thread Channel Guild,Public Thread Channel Guild,Private Thread Channel GuildForumChannelandGuildMediaChannel. A thread flattens Fluxer's thread metadata, such asarchived,lockedandautoArchiveMinutes, and carries the bot's ownmembershipwhen Fluxer reports it. TheisThreadChannelfunction selects every thread type. A forum or media channel carries its tags and post settings, each optional because Fluxer leaves them out of some payloads, such asguildChannelDelete. Text and announcement channels gaindefaultandAuto Archive Minutes default. The new constants areThread Rate Limit Per User ChannelFlags,Thread,Auto Archive Minutes ForumSortOrderandForumLayout. Thechannels.getandchannels.fetchmethods can return any of these shapes, andchannels.fetchAllnever lists threadsPermissionsgainsManageThreads,CreatePublicThreads,CreatePrivateThreadsandSend, soMessages In Threads permissionBitsandguardsaccept their names..require Permissions MessageTypegainsThreadCreated(18) andThreadStarterMessage(21).MessageFlagsgains the server-managed bitsHasThread(32) andFailed(256), which message and webhook send and edit operations reject before dispatch.To Mention Some Roles In Thread Message.threadholds the thread a message started, andGuildreports whether the bot can use threads in a community. The testing fixtures gainList Summary .threads Active thread()andforumChannel()with theWireThreadandWireForumChanneltypes, andguildCreate()now includes an emptythreadslistThe new
client.threadsnamespace creates, changes, lists and searches threads and manages their members. Itscreatemethod starts a thread in a text or announcement channel,createFromMessagestarts one on an existing message, andcreatePostcreates a forum or media post with its first message, sending its files in the same multipart request. Theeditmethod archives, locks, renames or pins a thread and replaces a post's tags. For reading,fetchActivelists a community's active threads,fetchArchivedreads one page of a channel's archived threads, andsearchreads one page of matching threads, with the first message of each post in a forum or media channel. Every list carries the bot's ownmembershipon each thread it joined. A search that Fluxer answers with HTTP 202 while it builds the community's search index returns{ indexing: true }without a retryThread membership changes through
join,leave,addMemberandremoveMember, and thread members are read withfetchMember,fetchMembersanditerateMembers. WithwithMember, each member also carries the user's community membership, decoded with the thread's community from the channel cache or from one extra channel read. Failures areChannelvalues whose operation names theOperation Error threadsmethod, and a missing-permission rejection ofcreate,createFromMessage,createPost,edit,fetchArchived,search,addMemberorremoveMemberlists the permissions that method needsForum and media channels can be created and managed through
channels. Creating a channel acceptsChannelType.ForumandChannelType.MediawithavailableTags,defaultReactionEmoji,defaultSortOrder,defaultTagSetting,flags, atopicof up to 4,096 characters and, on forum channels,defaultForumLayout. Text and announcement channels acceptdefaultandAuto Archive Minutes defaulton creation, andThread Rate Limit Per User channels.editaccepts those two settings and every forum setting, including a completeavailableTagslist. On creation, a setting that the chosen channel type cannot have fails with reasoninputbefore any request. An edit cannot tell the channel's type, sochannels.editaccepts every setting and leaves that check to Fluxer. Both methods fail with reasoninputbefore any request when a value has the wrong format or range, such as more than 20 tags, while checks that need the channel or community, such as whether a tag emoji exists, stay with Fluxer. The newchannels,.create Forum Tag channelsand.edit Forum Tag channelschange one tag and return the updated forum or media channel. The.delete Forum Tag editForumTagmethod replaces the tag, so an omittedmoderatedvalue or emoji resets. A permission rejection of any of the three listsViewChannelandManageChannelsindetails.required Permissions Webhook clients can now reach threads.
WebhookMessageInputgainsthreadId, sent as thethread_idquery parameter, and, on new messages,threadNameto start a forum or media post with up to fiveappliedTagIds. The SDK rejectsthreadNametogether withthreadId,appliedTagIdswithoutthreadName, andthreadNameon a reply or forward before any request. ThefetchMessage,editMessageanddeleteMessagemethods takeWebhookwith aMessage Operation Options threadIdto reach a message in a threadThread gateway events now have their own typed events. The
threadCreateevent delivers aThreadCreateEvent, the thread plusisNewlyCreated, which is false when the bot joined or was added to an existing thread. ThethreadUpdateevent delivers the changed thread,threadDeleteaThreadDeletionwith the thread's ID, community, parent and type,threadListSyncaThreadListSyncwith the bot's active threads in a community or in some of its channels, andthreadMembersUpdateaThreadMembersUpdatewith the members who joined and the IDs of those who left. The"channel"handler partition keys thread events by their thread. Automatic event filtering suppresses unused thread dispatches, keeps every thread dispatch while the channel cache is enabled and keepsTHREAD_DELETEwhile the message cache is enabledThe channel cache now holds threads. A community snapshot fills it with the active threads the bot can view, leaving out a malformed thread with a
gatewayWarn record instead of failing the snapshot. Thread create and update events store the thread, a thread deletion removes it, and a thread list replaces the cached threads it covers. The.thread Skipped threads.fetchActivelist also enters the cache, while archived pages, search results, forum posts and member reads do not. A change to the bot's own membership of a thread removes that thread, and a completed Resume removes the resumed shard's threads, because Fluxer never replays thread lists. Achannels.fetchAllresult keeps cached threads, and an operation confined to one thread clears only that threadDeleting a text, announcement, forum or media channel deletes its threads without thread deletion events. The channel cache removes the threads whose parent is the deleted channel, and the message cache removes the messages of every channel in that community, or without community context, that the channel cache does not hold. Without the channel cache that is every such message, because a message does not record its thread's parent. A thread deletion removes the thread's messages
Permission checks now handle threads. A thread has no permission overwrites of its own, because Fluxer calculates its permissions in its parent channel and then grants
SendMessagesin the thread exactly whenSendis granted. The newMessages In Threads Permissionsupplies that parent toInput .parent Channel permissions, which applies the same rule. A thread without its parent, a thread with a different channel as.calculate parentChannel, and aparentChannelbeside a channel that is not a thread fail with reasoninput. For a thread,permissions.fetchalso reads the parent channel under the same deadline. Theguardsguard now reads the thread and then its parent channel, each from an enabled channel cache before a request, and decides by the same rule, where it previously used only the member's role permissions and ignored the parent channel's overwrites. None of these checks covers whether the member can see a private thread or whether the thread is archived or locked. The guard also denies a command, with the reason "This command cannot confirm this channel's permissions", when a fetched channel that is not a thread lacks its permission overwrite list.require Permissions Message search no longer fails when a result comes from a thread. Fluxer lists the channel of such a result in a separate thread list, which previously made
messages.searchreject the whole page and stoppedmessages..iterate Search Messagenow holds those threads as typed thread shapes, each with the bot'sSearch Results Page .threads membershipwhen the bot has joined it, andchannelskeeps the other channels. Each message's channel appears exactly once acrosschannelsandthreads. Audit-log pages andguildevents also no longer fail on a forum or media channel change, because itsAudit Log Entry Create available_tagsanddefault_values now decode asreaction_ emoji ForumTaglists andForumDefaultReactionobjects, whichAuditLogChangeValuegains, andAuditLogPage.threadslists the threads that the page's thread actions targetRejections of thread, forum tag and webhook post operations carry a reviewed
apiError. The newApiErrorCodecategories arethreadArchived,threadLocked,threadAlreadyCreated,channelHasThreads,invalidChannelType,forum,Tag Names Not Unique forumTagRequired,noTagsAvailable,mediaChannelRequired,webhookandForum Target Required webhook, each with a hint that names the fix. The active thread, forum tag, pinned post and thread member limits joinThread Name Requires Forum resourceLimit, and a missing forum tag or thread member joinsunknownResource.ApiProviderCodegains the matching Fluxer codesMigration: Code that matched
type === "unknown"for a thread, forum channel or media channel now receives the typed shape, and an exhaustiveswitchoverGuildChannel,ChannelType,ChannelCreateorApiErrorCodeneeds the new cases. Apermissionscall for a thread passes the parent channel as.calculate parentChannel. Agatewaylist that names a thread dispatch an enabled cache needs, which is any thread dispatch for the channel cache and.ignored Events THREAD_DELETEfor the message cache, now fails client creation withConfigurationError, so remove the name or turn off that cache. With only the message cache enabled, deleting a text, announcement, forum or media channel now evicts every cached message of its community and every cached message without community context -
7733b23: Check every validated timestamp, whether received from Fluxer or supplied as a pin cursor, community history cutoff or embed time, with one ISO 8601 grammar: A date,
T, hours, minutes and seconds, an optional fraction of any length and aZor±hh:mmoffset, on a real calendar day. Accepted values stay exactly as received or supplied. Custom status expiry inputs keep their own rules, which also allow omitting seconds, and attachment expiry times remain unchecked text.Guildnow accepts an offset instead of only UTC, discovery times and community history cutoffs from Fluxer accept offsets, and pin cursors and message, pin and community times accept fractions longer than nine digitsEdit .message History Cutoff Migration: Member join and timeout times, invite times, ban times and channel last-pin times received in any other form, such as without seconds or a zone, are now rejected. Before, only the date was checked and
Date.parsehad to accept the value -
7733b23: Every operation that takes an input, query or options object rejects a key it does not support before sending anything, in both API styles. The failure is the operation's own error with reason
inputand aninputValidationconstraint ofallowedFields. Its explanation names the key, suggests the closest supported key and lists the supported keys, for exampleUnsupported option "timeout" in the operation options. Did you mean "timeoutMs"? Supported options are timeout Ms, signal Operations that sent the request and ignored an unknown key before now reject it: The options of message sends, replies, forwards, edits, deletions, fetches, searches, pins and reactions, the OAuth
authorizationUrl,exchangeCodeandrevokeinputs, and the default APIgateway.sendoptions. Channel position entries and permission overwrites report an unsupported key separately instead of as a general format failure. Theinstance.resolveandcache.entriesoptions name an unsupported key and suggest the closest one in theirConfigurationErrorMigration: Remove keys that an operation does not support from its input, query and options objects. Each failure names the first unsupported key it finds
-
7733b23: Log startup, readiness, lost connections with the close-code meaning and the real next step, session resets, long rate-limit waits, event drops, shutdown and every application failure by default. Records share one shape with a level, category, stable code, message and safe fields, and never contain the token, authorization values, client secrets, webhook tokens or invite codes
Configure output through
loggingwithlevel, per-categorycategories,debug,format,sink,dedupeandunsafesettings, or setFLUXERLY_DEBUG=1or a list of categories for Debug records. The default API prints readable lines in a terminal and JSON lines otherwise, deciding separately for standard output and standard error. Readable lines show the record code after the category, print safe details for every error in the cause chain and collapse stack frames inside the SDK and Effect into one line. TheFLUXERLY_LOG_FORMATandFLUXERLY_LOG_COLORenvironment variables choose the format and color for a client withoutformat. The color variable accepts1,true,yesoronto force color and0,false,nooroffto disable it, and an unrecognized value of either variable logs onesdkWarn. Without.unknown Environment Value sinkorformat, the native Effect API logs through the caller's Effect logger withfluxerly.*annotations, includingfluxerly.error.code,fluxerly.error.hintandfluxerly.error.*details for a failure. Each error in the cause chain adds its own annotations underfluxerly, then.error .cause. fluxerlyand so on. Its records carry masked copies of failures that show their code, so Effect's cause printer never shows credentials or extra error properties. Repeated Warn and Error records are collapsed with a repeat count, and.error .cause .cause. diagnostics()counts failures, drops, retries, rate-limit waits, reconnects and resumes. A rate limit whose wait would pass the request deadline fails the request at once, logs.counters ratelimit.deadlineand is not counted as a wait. Failure reports logged instead of reaching anonErrorhook carryfields.reportOutcome(queueFull,interrupted,clientClosedorsubscriptionClosed), and collapsed records carry the suppressed count infields.repeated. Thelifecycle.retryandlifecyclerecords carry.connection Lost fields.next, which isresumeoridentifyand names the next handshakeUnsafe payload logging prints its Warn banner once before the first payload record at any level, prints nothing for a
silentcategory and reads at most the first 64 KiB of each received REST bodyThe native API also records
fluxerly.*tracing spans for gateway connection, REST requests, event handling and command execution, plus REST duration, rate-limit wait, reconnect, drop and handler-failure metricsMigration: The
development,measurements,minimumLevelandloggerlogging settings,fromStructuredLoggerandfromEffectLoggerare removed. Uselevelandcategoriesfor levels,debugorFLUXERLY_DEBUGfor Debug records, andsinkto forward records to another logger. The typesDefaultLogger,Default,Logging Options StructuredLogger,SdkLifecycleEvent,Sdk,Lifecycle Log Record Sdk,Operational Log Record Sdk,Measurement Log Record SdkandMeasurement Operation SdkMeasurementStageare removed.SdkLogLevelis renamed toLogLeveland its values are now lowercase, such as"info". TheAllandNonevalues are removed, andlevel: "silent"replacesNone.SdkLogRecordis renamed toLogRecord, which has a new shape. Clients now print startup, readiness, connection loss and shutdown at Info by default. Setlevel: "warn"to print only problems. The Effect API'sMessageis nowCache Options<E, R, M> Message, because cache callback failures go to the client'sCache Options<M> onErrorrather than a cache-specific hook -
abb5d3f: Add
WebhookTypeand theIncomingWebhook,ChannelandFollower Webhook UnknownWebhookshapes to both APIs. Received metadata now requires a kind and preserves future numeric kinds astype: "unknown"withrawTypeBot clients can rename, move or delete follower webhooks but cannot change their avatars. Deleting a follower webhook stops its channel following the source. Optional frozen
sourceGuildandsourceChannelsnapshots reflect source visibility to the original creator, and their absence does not prove deletionWebhook metadata no longer carries tokens. The
Createdfield and webhook-clientWebhook .webhook fetchandeditresults now useIncomingWebhook, and creation requires a valid incoming token before returning credentials
Minor Changes
-
2c9dfde: 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 channels.editwithavailableTagscan, 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 aschannels.create,channels.reorderorguilds.leave, 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 callclientafter the write.cache .clear( "channels") Behavior change: A
guildChannelUpdateevent for a forum or media channel now removes the channel's cached posts that apply a tag missing from the update'savailableTags, 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 updateBehavior change: A
guildChannelUpdateevent 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 returnBehavior 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
guildCreate, 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 throughsharding.sessionsis not a new session and keeps themThe new
rolesmethod returns every cached role of a community, including everyone and ordered like.get All( guild Id) roles.fetchAll, orundefinedunless the cache knows the list is complete. AguildCreateevent makes it complete, as does an uncontestedroles.fetchAllread 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 nextguildCreateor full read. Therolescache limit counts the roles of every community together, so a bot in many communities needs a largermaxEntriesto keep complete listsThe 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 amembers.fetchSelfresult. Withcache.membersenabled, eachguildCreatenow stores the bot's own member. TheguildCreateevents 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. AguildCreatesent 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 channelsThe 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 guildCreate. It is the shard's current value, so an event still waiting in a handler's queue can already see a newer oneThe new
Eventfield gives the receiving shard'sContext .session Epoch sessionEpochwhen 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. Comparecontext.sessionEpochper 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 wayThe new
contextfield ofBotEventContextgives eachrunBotevent handler, in both APIs and in bots run bycreateTestBot, the sameEventContextthat aclient.onhandler receives, so arunBotbot can comparecontext.sessionEpochper community as well -
321d8bb: 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) suggestionofCommandUnknownName: 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 returnsundefinedwhen 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 guildId, whichmemberdescriptors need. Invalid input to either helper, such as a bad descriptor, a non-string name or candidates that are not an iterable of strings, throwsConfigurationErrorin both APIs, like other registration misusePrefixand theCommand Parse Rejection Commandoutcome ofParser Rejected onUnmatchedhave an optionalname, the command name the parser read before it declined, as typed. Thecommands.parseQuotedparser 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. -
0f8a442: Add
defineBotto the default and Effect entry points. It returns a bot'srunBotoptions 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 bothrunBotandcreateTestBotThe starter bot in
examples/starternow exports its options asbotwithdefineBot, answers!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 -
5717638: Behavior change: The default
maxPagesof an iteration now grows withmaxItems, so a caller who sets onlymaxItemsgets up to that many items. It is the pagesmaxItemsneeds at the page size plus 100 spare pages for short pages and skipped duplicates, instead of a fixed 100. For example,messageswith.iterate History maxItems: 10000and the default page size of 50 used to fail withPaginationErrorreasonpageLimitafter 5,000 items and now reads all 10,000. This applies toiterateHistory,members.iterate,members,.iterate Search guilds.iterate,auditLogs.iterate,iterateReactionUsers,iteratePins,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 pageLimit. An explicitmaxPagesis used as given, including a value above or below the new default, except thatmessagesnever reads more than 400 pages. Code that relied on the old default to stop a long scan early can set.iterate Search maxPages: 100Behavior change: The
directMessages.openanddirectMessages.sendmethods 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 asopen("bad"), fails before the cache is touched and leaves it intact. Code that relied on the full clear can callclientafter the write.cache .clear( "direct Messages") The
PaginationErrorhint for reasonpageLimitnow tells callers to setmaxPagesexplicitly or lowermaxItems, and to narrow the filters of a message search, which reads at most 400 pages. It no longer suggests a largerpageSize, because the defaultmaxPagesshrinks aspageSizegrows -
321d8bb: 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) "notDispatched","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 forCancelledError,SdkDefect, non-SDK values and aCausewith a defect, an interruption or a failure without an outcome, so the caller applies its own policy. Only"notDispatched"shows that a write was not sent, so treat every other value, includingundefined, as a write that may have applied and reconcile before sending it again. ACausecovers only the failures it kept, and Effect's concurrentforEachandalldrop 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 inEffect.timeoutyields only aTimeoutError, so use the operation'stimeoutMsoption to keep the outcome.The new
errorsfunction is true for an SDK error with HTTP status 404, including a 404 that.is Not Found( error, resource?) messages.sendreports with reasonrejected. Pass aNotFoundResourcesuch as"member"or"channel"to also require Fluxer's matchingUNKNOWN_*code. A 404 says only that the resource is missing, not why: Fluxer answersUNKNOWN_CHANNELfor 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 403missingPermissions. A 404 without a recognized code is true without a resource and false with one. Any other resource name throwsHelperErrorwith operationerrors.isNotFoundand the new reasonresource. TheNotFoundResourcetype is exported from both entry points.The new
textfunction shortens text to at most.truncate( content, { max Length, suffix? }) maxLengthUTF-16 units and ends it withsuffix(default"…") when anything was cut. It never splits a surrogate pair and does not trim or look for word boundaries. Invalid text or options throwHelperErrorwith operationtext.truncate. TheTextTruncateOptionstype is exported from both entry points.ClientClosedErrorgainsoutcome, an addition. A REST call ended by a closing client now reports how far it got:notDispatchedwhen the client was already closed or the request was still queued,rejectedwhen closure came while waiting out a 429 before a retry, andunknownwhen the request was in flight. The outcome is also recorded indetails.outcome, and anunknownoutcome 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 reportsnotDispatchedfor the message request even if the earlier step took effect. A closure that cuts off a read recordsdetails.readand gets no hint to check for an applied change. The POST readsmessages.search,members.searchandattachmentscount as reads in a closure and in their other failures, so their network and timeout failures are now retryable by.refresh Urls errors.isRetryable. An OAuth request that closure cuts off in flight still fails withOAuthErrorreasonnetworkand outcomeunknown, and a closure during OAuth instance discovery now reportsnotDispatched. A message cleanup closed while a batch waits out a rate limit now reports that batch'srejectedoutcome instead ofunknown, and a closure between batches already reportednotDispatched. The constructor takes an optional{ outcome, read }object, so existingnew Clientcalls are unchanged.Closed Error() Partitioned
inviteCreatehandlers now order events per community. They previously ignored the invite's nestedguild.idandchannel.id, so every invite shared one key and ran one at a time. Withpartition: "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 aninvalidFormBodyAPI error. -
78579fe: 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 -
b1b9d72: Fields that Fluxer documents on gateway events and the SDK used to drop now reach handlers, in both APIs
A
messageCreateevent carriesmentionedHere,channelTypeandnicknames, and themessageFieldsoption accepts them. They are set only on a created message, somessageUpdateevents, 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 astrue. 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 mentionedEveryoneA
messageDeleteevent carriesmember, a new frozenDeletedMessageMemberthat is aGuildMemberwithoutusernameandisBot, 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 itVoice states from
voiceStateUpdate,voiceStateSnapshotand the call events carryisE2eeCapableandisPeerToPeer, which readfalsewhen omitted, andversion, which is absent when Fluxer sends none. Behavior change: A voice state whosee2ee_capableorp2pis not a boolean, or whoseversionis not a non-negative integer, is now rejected like the other malformed voice state fieldsA
guildCreateevent carriesmemberCount,onlineCountandjoinedAtas 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, soguilds.fetchCountsremains the way to read current totalsThe testing entry points'
WireMessageaccepts optionalchannel_type,nicksandmention_here, sofixturesemits a created message in full.message( { channel_ type: 3 }) -
8b70a5c:
GuildletsFeature Toggles .Visits Enabled guilds.editkeep community visits on. Fluxer now enablesVISITS_ENABLEDfor new communities and removes every toggleable feature that afeatureToggleslist leaves out, so an edit without it turns visits off and ends current visits. Before this, the SDK rejectedVISITS_ENABLEDlocally, so everyfeatureTogglesedit turned visits off -
f0f217a:
ResolvedInstancehas a newdiscoveryEnabledflag, in both APIs, that reports whether the instance has community directory discovery switched on. Fluxer documents a missing flag as enabled, so it readstruewhen the instance omits it. While it isfalse, the discovery namespace'ssearch,apply,editandwithdrawfail with reasonrejected, HTTP400anddetails.providerCodeDISCOVERY_DISABLED, whilefetchStatusandfetchCategoriesstill answer, andfetchStatusthen reportseligibleasfalse. 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 -
7611ba6: Behavior change:
messages.keepTypingnow returns the task's own result when a later typing refresh fails. Before, the default API returnedErrwith 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 fields.channelIdnames the channel and whoseerrorholds 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 asSdkDefectin the default API and in theCausein 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
ClientClosedError(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'sstateorobserveStateinstead -
c892c2c: Both
Clientinterfaces have a new read-onlylastReceiptproperty: The receipt of the most recently accepted event, asEventContext.receiptnumbers 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 compared -
470983c: The member that Fluxer attaches to community events now reaches handlers. The
memberfield ofmessageCreateandmessageUpdatemessages, ofmessageReactionAddandmessagereactions, and ofReaction Remove typingStartnotices carries the author's or actor'sGuildMember, 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,messageDeleteevents (Fluxer sends no username there) and batch or clear events. ThemessageFieldsoption now acceptsmember, 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
cache.membersenabled, these events also store their member in the member cache, whichevermessageFieldsthe client selects, somembers.getanswers for an author without a REST read. A member that cannot be read removes the account's cached member and logsgateway, 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'
WireMessageaccepts an optionalmemberwithout its user and guild ID, sofixturesemits a decorated message in tests.message( { member }) -
b54fbd4:
clienthandlers now run up to eight messages at a time by default in both the default and Effect APIs, matching.on( "message Create", ...) runBotand the command router. Before, acreateClientbot ran them one at a time, so one slow message handler stalled every later message, while arunBotbot did not. Handlers of other events still run one at a time unlesspartitionis 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 -
18d4a84: 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() userIdto 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 aspermissionsand.calculate permissions.fetch. The result is a frozen array of frozenChannelPermissionsentries ({ channel, permissions }), in the order Fluxer lists the channelsFluxer lists only the channels the bot can view, and no threads, so call
permissions.fetchwith a thread's ID for one. Theoptionsare the plainRequestOptions, withoutpreferCache, because no cache holds a complete channel list. Failures useGuildOperationErrorwith the new operationpermissions, 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.fetch, with reasonresponse, outcome"unknown",details.readanddetails.channelIdThe 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) missingPermissionsfor the rest. Pass the overwrite's currentallowanddenyascurrentand 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 throwsHelperErrorwith operationpermissionBits .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) HelperErrorwith operationpermission, 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) retryAfterMsof any operation error orRateLimitError. For aShardConnectionErrorit returns the wait of the failure that stopped its shard, and for an EffectCausethe 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, aserrors.outcomedoes. It is true only when theCausehas at least one reason and every reason is a retryable failure, so a defect, an interruption or an emptyCausegivesfalseEventContexthas a newreceiptfield 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 asguildCreateand itsvoiceStateSnapshot, get consecutive numbers in dispatch order. Handlers of one community can run out of order because of concurrency, partitions orpartitionReady, so compare receipts to tell which event came first. Under a supervisor each child process numbers its own eventsA validation error with Fluxer's code
INVALID_URL_FORMATnow 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 domainAuditnow documents the shape Fluxer records: A change with the keyLog Actions .Member Role Update roleswhoseoldValueandnewValueare the member's full role ID lists, so the added roles are those innewValuethatoldValuelacks. AMemberUpdateentry that changes roles carries the same changeTesthas a newDisconnect Options endSessionoption, defaultfalse, in both testing entry points. WithendSession: true, the test gateway forgets the shard's session beforedisconnectcloses 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'ssessionEpochincreases, 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 throwsConfigurationError, and the nativedisconnectdies with itWithout
endSession, 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.Testnow says thisDisconnect Options .code -
80e4a00: 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
partitionReadyoption ofclient.onand of arunBotevent handler (EventHandlerOptions,BotEventOptions) 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 towardmaxPendingMessages,maxPendingBytesand a draining shutdown. The option needspartitionand otherwise fails withConfigurationErrorwhosefieldis"partitionReady", a new member of theConfigurationunionError .field Dropped events are also counted per event name.
ClientCountershas the neweventsDroppedByEventmember, which counts overflow, closed and collector drops and malformed dispatches whose type names exactly one event, and theeventsDroppedreason 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 metricfluxerly_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" client.onandrunBot, 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. UndermaxPendingBytesthe 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. Partitionedcommandsdelivery follows the same rule. Handlers without a partition and the policiesdropNewestandstopare 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
-
989089a: Command routers can now wait for a partition's own setup before its commands run. The
partitionReadyoption works forrouter.attachand for thecommandsoption ofrunBotandcreateTestBot(BotCommandsOptions), in both APIs, with the same rules as thepartitionReadyoption ofclient.on. 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 needspartitionand otherwise fails withConfigurationErrorwhosefieldis"partitionReady". A hook that fails reports each waiting message of that key to theonErroroption of the commands, and those messages run no commandBehavior change for command routers that have an
onError: A failingpartitionReadyhook, a throwingpartitionfunction and an overflow report of the router's subscription now reach thatonError, in arrival order with the command failures. Before, a throwingpartitionfunction and an overflow report went to the client-levelonErroror the log, so a bot that replied fromcommands.onErrorstayed silent. A router without its ownonErroris unchangedIn the Effect API the hook returns an Effect, and its required services are added to the requirements of
runBotandcreateTestBot, which gain a trailing type parameter for them, andBotCommandsOptionsgains a fifth one -
321d8bb: Permission and role helpers now cover cache-first reads, role rank, timeouts, partial overwrite changes and readable permission names, in both entry points. The
permissions.fetchmethod takespreferCache: 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 }) RoleManagementInput) check rank from held snapshots, andmembersreports.timeout State( member, { now }) "none","active"or"unknown"fromcommunication. TheDisabled Until permissionhelper (Bits .patch Overwrite( current, { allow, deny, inherit }) OverwritePatch) changes named permissions of an overwrite and keeps every other bit, andpermissionreturns Fluxer's English name for a permissionBits .label( name) Behaviour changes to know about:
- Missing-permission hints and the
requirePermissionsguard 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. Thedetailsfield still holds.required Permissions Permissionskeys - The
requirePermissionsguard now decides throughpermissions.fetchwithpreferCache, 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 command - A fetched community channel that is not a thread and comes without a permission overwrite list fails
permissions.fetchwith reasonresponseon 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,details.channelIdanddetails, and carries a hint. A cached channel without the list is fetched again. With.response Field preferCache, a closing client now fails a cache lookup withClientClosedErroroutcome"notDispatched", as fresh reads do - The
channelscomment now says that Fluxer requires.remove Permission Overwrite ManageRoles, and a bot without Administrator can remove only an overwrite whose denied permissions it has all of
- Missing-permission hints and the
-
c892c2c: Handlers of an update or delete event now receive the cached value from before the change, as
previouson theEventContextthatclient.onhandlers,runBothandlers and event middleware receive in both APIs, so edit and deletion logs can show what changed. ThemessageUpdate,messageDeleteandmessageDeleteBulkevents read the message cache,guildMemberUpdateandguildMemberRemovethe member cache,guildRoleUpdateandguildRoleDeletethe role cache,guildChannelUpdate,guildChannelDelete,threadUpdateandthreadDeletethe channel cache, andguildUpdateandguildDeletethe community cache. The snapshot is the frozen value the client's own cache held just before it applied the event, never fetched. AmessageDeleteBulksnapshot is a frozen list of the messages the cache held, once each in the order of the event'sids, 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, andmaxPendingBytesdoes not count it. The test clients deliver it the same way when their cache option is enabledThe new
PreviousSnapshotsinterface lists the snapshot type per event, and the newPrevioustype gives the type ofSnapshot<K, M> previousfor one event.EventContextnow takes optional type parameters for the event and message type, andEvent,Invocation Of .context Botand theEvent Context .context client.onhandler's context are typed for their event. A helper that takes a plainEventContextstill accepts any handler's context, including one from a client withmessageFields, and a context built by hand, such as in a unit test, can leavepreviousout -
ab97759: The
ratelimit.waitandratelimit.deadlinelog records and therateLimitobservation now repeat what a429response sent about the limiter that answered it. Log records carry these values infields, andRateLimitObservationhas them as optional members:scope,bucket,limit,remainingandresetAfterSecondsfrom theX-RateLimit-Scope,X-RateLimit-Bucket,X-RateLimit-Limit,X-RateandLimit-Remaining X-Rateheaders,Limit-Reset-After retryfrom a numericAfter Header Seconds Retry-Afterheader, andretryAfterSecondsandbodyGlobalfrom the body'sretry_afterandglobalmembers. Each value is present only when the response sent it in the expected form. Aratelimit.deadlinerecord for a request refused before sending, behind a known pause, has none. Waits, retries and the existingglobalvalue are unchanged -
d63165f: Roles can have icons.
GuildRolenow hasicon, the image's hash, next tounicodeEmoji, andassets.roleIconbuilds the image's URL.RoleCreateandRoleEdittakeicon, an image data URI, andunicodeEmoji, where null removes the icon and setting one replaces the other.RoleCreatealso takesiconSourceRoleIdto 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 field -
cd412ec: Behavior change:
text.truncate,text.splitand the pages ofrouter.helpnow cut at a grapheme boundary, asIntl.Segmenterdefines 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,textreturned.truncate( "aaaaaaaaa🇩🇪", { max Length: 11, suffix: "" }) "aaaaaaaaa🇩", half a flag, and now returns"aaaaaaaaa". A piece or truncated result can now be shorter thanmaxLengthand never longer, and the newline and whitespace preferences oftext.splitonly choose positions between characterstext.truncatekeeps whole characters only. When the first character does not fit before the suffix, it returns the suffix alone, sotextnow returns.truncate( "🇩🇪x", { max Length: 4 }) "…"instead of half a flag, and a letter with more combining marks than fit is dropped entirely.text.splitstill cuts a single character that is longer than the limit between code points, because it must advance, and it still throwsHelperErrorwhen the limit cannot hold one code point. Joining the pieces oftext.splitstill reconstructs the input exactly, except that each lone surrogate becomes U+FFFDNo 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 -
419fb51: The
text.truncateandtext.splithelpers now replace each lone surrogate with U+FFFD (�) instead of throwingHelperError. Ordinary code can create one, for exampleslicecan 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, somaxLengthstill holds, andtext.splitpieces join tocontent. 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() -
a47b104: The package installs a
fluxerlycommand. Runningnpx --no fluxerly agents,pnpm exec fluxerly agentsorbunx --no-install fluxerly agentsin an application's folder copies the SDK's rules for coding agents into itsAGENTS.md, which coding agents read automatically, and records the installed SDK version. Running it again replaces only that section, and a broken section marker stops the command without changing the fileThe agent guide at
agents/AGENTS.mdstarts with those rules, includes the starter bot and a Result check, and lists patterns that do not exist in Fluxerly. The package's entry points name the guide in their overview -
abb5d3f: Add
channels.follow,channels,.fetch Follower Stats messages.publishandmessagesto both APIs for announcement-channel follows, follower counts, message publishing and public source-community lookups. Follow results identify the source channel and the created follower webhook, which can be deleted to stop following. After publishing, the cached source is updated and copies enter the cache only when they arrive. After an unknown outcome, the SDK drops the source from the cache and does not publish again.fetch Crosspost Source The
errors.apiCodehelper now names announcement, follow, conversion, publishing and published-message editing rejections, such asinvalidFollow Target Channel -
c4f4f49: Add
ownerIdto the result ofclientin both entry points, the decimal user ID of the account that owns the bot's application. The result keeps no other owner details, and a response without a valid owner ID now fails with.application .fetch() Botand reasonApplication Operation Error "response".guards.ownerOnly()called without IDs now allows only that owner. The first invocation reads the owner withapplication.fetch, and the guard keeps the ID for the client's lifetime. A failed read keeps nothing and fails the command, and the next invocation reads again. Explicit owner IDs work as before, and anundefinedargument still throwsConfigurationError -
7733b23: A client logs one
lifecycle.connectedrecord once every shard it owns is connected, such asConnected to Fluxer as My. When the bot owns every shard and is in no community, the record is a Warn that includes the bot's installation linkBot in 3 communities Loading any entry point on a Node.js version older than 24.15 throws an error that names the required and the found version, instead of failing later with an unrelated error
Several configuration errors explain the likely fix:
- An event named
ready,clientReady,onReadyorconnectedpoints to the connected record, thesetupoption ofrunBotandclient.connect() - An unsupported command argument type suggests the supported type it most likely meant, such as
textforstring, and lists every supported type - A missing bot token mentions
node --env-file=.env bot.js, the current folder and a.envfile saved as.env.txt - A command definition placed next to
prefixin thecommandsoption ofrunBotis explained as belonging in the innercommandsobject, an unknown key there listscommandsandonErroramong the supported keys, and a missingprefixis reported as required
- An event named
-
139f4dc: Add atomic keyed command batches with per-command argument inference, plus command-context reply helpers that bind the incoming message and handler cancellation
-
3d68f4d: The
clientmethod now bounds the work each listener can hold, in both APIs. A listener whose calls never finished, such as one returning a promise that never settles or an Effect that never ends, kept one unfinished call per cache change without limit. A listener now has at most.cache .on Change concurrencyunfinished calls, 256 by default, and up tomaxPendingChangeslater changes, also 256 by default, wait in the order they were applied. A change arriving beyond that drops the oldest waiting change, logs acache.changesDroppedWarn with the observer's newidand how many changes it has dropped, and counts the drop indiagnostics(). Both limits go in the new optional second argument,.counters .cache Changes Dropped CacheObserverOptions, and{ concurrency: 1 }handles changes one at a time in order. A limit that is not a positive safe integer, or an unknown key, throwsConfigurationErrorin the default API and dies with it in the Effect API. A synchronous listener finishes each call before the next change, so the limits never delay or drop its changes. Shutdown does not wait for unfinished listener calls, so changes still waiting at shutdown, including its final clears, are dropped and recorded the same way -
7733b23: Add
clientto observe changes to the client's local caches as.cache .on Change( listener) { kind, op, key }records, whereopisset,deleteorclear. Every cache kind reports stored and replaced entries, removals including expiry and capacity eviction, and whole-kind clears fromcache.clear(), connection gaps of unknown scope and shutdown. Keys use decimal IDs, such aschannelId:messageIdfor messages andguildId:userIdfor members. Changes are delivered in applied order after the cache work that produced them, a failing listener is reported as a cache failure, and no change is recorded while no listener is registered -
3d68f4d: Each category in
clientnow keeps running totals since the client was created, in both APIs. The.diagnostics() .caches hitsandmissesvalues count local lookups such asclient.messages.get, including lookups the SDK makes itself, and theevictionsvalue counts entries the cache removed on its own, withcapacityfor room withinmaxEntriesormaxBytesandexpiryfor entries older thanmaxAgeMs. Removal by events, writes,cache.delete, clears, connection gaps or shutdown is not counted, and a disabled category reports zero -
7755411: The
clientmethod now releases a single cache kind, and the new.cache .clear( kind) clientremoves one entry by the key format that.cache .delete( kind, key) cache.onChangereports, such asguildId:userIdfor members orchannelId:messageIdfor messages. Both work in the default and Effect APIs, run synchronously and report aclearordeletechange tocache.onChangelisteners. Reads already in flight cannot restore what was removed. An invalid kind or key throwsConfigurationError, whosefieldiskindor the new valuekey -
7733b23: Add the
callCreate,callUpdate,callDeleteandentranceSoundPlayevents for Fluxer's CALL_CREATE, CALL_UPDATE, CALL_DELETE and ENTRANCE_SOUND_PLAY dispatches, with frozenCallCreate,CallUpdate,CallDeleteandEntranceSoundPlaypayloads. Call participants useCallVoiceState, which omits Fluxer's internal voice routing fields. A malformed dispatch of these types is skipped and counted like other malformed events, without clearing any cache -
1afd073: Preserve provider-supplied guild context as optional
guildIdonmessageDelete,messageDeleteBulkandchannelPinsUpdatepayloads through both APIs. No channel lookup or cache inference is added, and omission does not establish that the event came from a private channel -
c4f4f49: Add
fluxerly init, which writes a JavaScript, TypeScript or Effect starter bot into the current folder. In a terminal it asks which starter to write, with JavaScript as the default. The--template js|ts|effectoption chooses without asking, and without a terminal or the option it writes the JavaScript starter. An unknown template prints the usage, writes nothing and exits with status 1. Each starter writes apackage.jsonwithstartandtestscripts that run without a build step, the bot with the starter's!pinghandler, a test that talks to the bot throughsayfrom@neontechspace/or, for Effect,fluxerly/ testing @neontechspace/,fluxerly/ effect/ testing .env.exampleand its copy.env, a.gitignorefor.envandnode_modules, and theAGENTS.mdthatfluxerly agentscreates. The TypeScript and Effect starters writebot.tsandbot.test.ts, which Node.js runs directly, and a stricttsconfig.jsonfor editors and acheckscript that runs the compiler. It replaces no file. When any of these files other than.envand.gitignoreexists, it lists them and writes nothing. An existing.envis kept, and an existing.gitignoregets only its missingnode_modulesand.envlines. It then installs the SDK, Effect for the Effect starter, and TypeScript 7 and the Node.js types for the TypeScript and Effect starters, with the package manager that ran it, npm, pnpm or Bun, and prints the next steps with that manager's test and start commands and, for those two starters, its type-check command. The--no-installoption skips the install and prints the install commands instead, for offline or CI use. A failed install keeps the written files, prints the install commands and exits with status 1 -
7733b23: Add the
observeclient option to export metrics and traces from either entry point to any monitoring system. The observer receives one frozenObservationfor each finished REST request attempt, with its method, route template, status, duration and attempt number, each rate-limit wait, each reconnection attempt, each resumed session, and each finished handler or command invocation, with its duration, outcome and the error name and code of a failure. Observations never contain tokens, payloads or message content, and route templates replace IDs with placeholders. The observer runs synchronously and independently of the logging settings. An observer that throws is counted indiagnostics()without changing SDK work. The Effect API keeps its Effect metrics and spans and calls the same observer.counters .sink Failures -
cae766a: The new
client.schedulemethod runs a task once afterdelayMsor repeatedly withintervalMsbetween runs, owned by the client's lifetime in both APIs, so arunBotcommand, handler orsetupcan start delayed or repeating work without keeping a command slot busy or leaving a timer behind. It returns aScheduledTaskwhoseclose()cancels later runs. ThedelayMsvalue is 0 through 2,147,483,647 andintervalMs1 through 2,147,483,647, and a repeating task first runs afterintervalMsunlessdelayMsis set. Invalid options throwConfigurationErrorin the default API and die with it in the Effect API. The default API takes a function that receives a cancellation signal, and the Effect API takes an Effect that keeps the caller's services and gets a Scope of its own for each run. Each failed run is reported to the client-levelonError, or logged at Error with the new codelifecycle.taskFailedunder the new failure kindtask, and a repeating task keeps its schedule after a failure. A draining shutdown, including a requestedrunBotstop, lets running tasks finish withindrainMsand counts them in the drain records' newtasksfield, and every shutdown cancels the rest, counting the tasks it cut short in onelifecycleInfo record. The test kit's.tasks Cancelled idle()andsayin both APIs also wait for scheduled task runs in progress, while a task still waiting for its time does not hold them up. Scheduling after shutdown began throwsClientClosedErrorin the default API and dies with it in the Effect API. The test clients treat a failed task run without anonErrorhook as an unhandled failure. Tasks are kept in memory only. The commands guide's reminder example now schedules its reply instead of waiting inside the command, and the bot lifetime guide explains scheduled tasks -
7733b23: Command contexts in both entry points gain
help(options?), which builds help pages from the router that matched the command, so ahelpcommand works in the keyedcommandsobject ofrunBotwithout a reference to the router. The prefix defaults to the one the invoking message used and the page length to 2,000 UTF-16 code units. The other settings matchrouter.help, and the newCommandtype describes them. For example:Context Help Options help: { execute: ({ help, reply }) => reply( help()[0] ?? "No commands") } -
ce65b67: The
clientmethod now accepts.logging .configure debugin both APIs, so Debug output can be changed while the client runs, including output turned on byFLUXERLY_DEBUG. Adebugvalue replaces the current Debug categories:truefor every category, a list for those categories, andfalseor an empty list for none. An omitteddebugkeeps the current categories, while an omittedlevelorcategoriesstill returns to its default.LogLevelSettingsgains thedebugsetting -
7733b23: Make errors and log records say what went wrong and what is accepted.
HelperErrorandAssetUrlErrormessages name the helper and the accepted form, for exampleHelper operation format, instead of.user Mention failed: I Ds must be decimal strings from 0 through 9,223,372,036,854,775,807 Invalid input for format..user Mention PaginationErrormessages include the input explanation or what stopped the iteration, and the error carries ahint: The rejected path for invalid settings, raisingmaxPagesforpageLimit, and retrying later forcursorStalledandindexingInput validation explanations name the field in words and state the accepted range with its unit, such as
ms,bytesorseconds. Presence explanations name the field they describe, unsupported-field explanations list the accepted fields or the type that defines them, timeouts state theirmsunit, and ban and timeout durations state their range inmswith the matching days or years. Connection, startup and shutdown records use full sentences, such asShard 1 lost its connection. Close code 4000 (, and configuration errors name the full option path, such asUNKNOWN_ ERROR): The gateway reported an unspecified error. Reconnecting and resuming the session in 486 ms connection..startup Timeout Ms SupervisorErrorandSupervisorChildErrormessages describe the failure and carry a hint. An instance discovery failure keeps its transport error code and says what was wrong with the response.CancelledErroraccepts an optional operation, which is recorded asdetails.operationand named in the message, and its hint explains that signals the SDK passes to handlers abort when the client shuts downThe
inputValidation.pathandconstraintvalues are unchanged. Messages remain changeable text, so match oncode,reason,fieldandinputValidationand on log record codes instead -
7733b23: Provide a native client as an Effect service with
FluxerClientfrom the Effect entry point.Fluxercreates the client in the layer's scope and shuts it down when that scope closes.Client .layer( options) Fluxerreads the token, and optionally any other top-level setting, from EffectClient .layer Config( { token: Config .Redacted( "FLUXER_ BOT_ TOKEN") }) Config. The token stays redacted until the client receives it. A missing Config value fails the layer withConfigError, while a blank token or another invalid setting dies withConfigurationError. Building either layer does not connect. Register handlers, then callclient.connect()orclient.run() -
7733b23: Add
FluxerTestClientto@neontechspace/for applications that read their client from thefluxerly/ effect/ testing FluxerClientservice.Fluxercreates a test client withTest Client .layer( options) createTestClientand provides its client asFluxerClientand the whole test client, with its controls, asFluxerTestClient. Application code then runs against the in-memory Fluxer unchanged. Closing the layer's Scope shuts the client down and dies withUnhandledfor a handler failure the test did not readTest Failures Error -
7733b23: Read the community that owns a custom emoji or sticker with
emojisand.fetch Source( id) stickersin both entry points. Fluxer calls a community a guild. The result is a frozen.fetch Source( id) Expressionwith the community'sSource Guild id,name,iconhash and badgefeatures. Fluxer answers when the source community is discoverable or the bot is a member of it. A private or unavailable source community fails withGuildOperationErrorwhoseapiError.codeisunknownResource. Badge names added by Fluxer later are kept rather than failing the read, and a response missing any documented field fails with reasonresponse -
7733b23: The
membersmethod accepts.fetch Can Manage actorUserIdto check whether another member, such as the moderator who ran a command, outranks the target instead of the bot. It reads that member in place of the bot's own membership, and an invalid ID fails with reasoninputbefore any request. Both entry points name the option typeCanManageOptions -
314ac29: Add
fileto both entry points, a built-inSession Store( directory) SessionStoreforsharding.sessionsthat keeps one JSON file per shard. Saves write a temporary file with mode 0600, flush it and rename it over the shard's file, so a crash never leaves a partial snapshot. A missing file loads as no snapshot, and a corrupt file rejects without quoting its contents, so the session ID never appears in logs -
abb5d3f: Add the
guildHealthUpdateevent with a frozenGuildHealthUpdatepayload,{ guild, in both entry points. Fluxer sends it when the server hosting a community starts or stops lagging. It does not mean that the bot is disconnected or that the community is unavailable, and the SDK neither reconnects nor clears caches because of itId, degraded } -
1afd073: Add
Guildto distinguish provider-reported joins from startup and recovery availability snapshots through both APIs. ExistingCreate .is New Join guildCreatedelivery andconnect()readiness remain unchangedClassification requires a bot gateway that supplies Fluxer's availability marker. Legacy instances that omit it for startup snapshots cannot be classified reliably, and a join dispatch may replay during Resume
-
7733b23: Recognize Fluxer's
TWO_FACTOR_REQUIREDrejection in both entry points. A kick, ban, timeout or other action that needs a moderation permission in a community that requires two-factor authentication now fails withapiError.codeset totwoFactorRequiredandapiset toError .provider Code TWO_FACTOR_REQUIRED, instead ofapiErrorset tonull. Fluxer returns it when the account that owns the bot's application has no two-factor authentication enabled and the bot does not own the community. Communities, which Fluxer calls guilds, gain an optionalmfaLevelfield with the newGuildMfaLevelsconstantsNoneandElevated, so a bot can see this requirement before it sends a moderation request. The field appears on fetched communities, community list entries andguildCreateandguildUpdateevents. A community whosemfa_levelis present but not 0 or 1 is treated as malformed, like other malformed community fieldsThe moderation and message deletion references describe this requirement, and the
messages.deleteManyreference states that it requiresManageMessageseven for the bot's own messages -
7733b23: Add
snowflakes, which returns the shard Fluxer routes a community's events to, and.shard For( guild Id, total Shards) client, which returns this client's shard for a community or.shard Id For Guild( guild Id) undefinedwhen another process owns it. Fluxer calls a community a guild. Both APIs provide themFor an invalid ID, the helper throws
HelperErrorwith reasonid, and for a total outside 1 through 16,384 with the new reasonshardCount. The client method throwsConfigurationErrorfor an invalid ID -
7733b23: Pass an
EventContextwithshardIdandreceivedBytesto everyclient.onhandler, as the third argument in the default API and the second in the Effect API. The context names the local shard that received the event and the byte length of the gateway frame that carried itAdd
clientto run event middleware around every later.use( middleware) onhandler invocation, including those of existing subscriptions, command routers andrunBothandlers. Middleware runs in registration order, receives the event name, payload, context and subscription ID, and can skip the handler by not callingnext. Failures of middleware and of the handler inside it are reported like handler failures, so middleware cannot hide a handler failure. Both APIs return aMiddlewarewithRegistration close(). The default registration also works withusing, and the Effect API ends the registration when the executing Scope closes and runs middleware with the services available at registration -
7733b23: Add the
partitionhandler option toclient.onandrunBotevents, in both entry points, to keep related events in order while unrelated events run side by side. Events with the same key run one at a time in receive order, and events with different keys run in parallel up toconcurrency, which defaults to 8 when a partition is set. The value"guild"keys each event by its community, which the API calls a guild, and a direct message by its channel. The value"channel"keys each event by its channel, or by its community when it has no channel. A function receives the event and returns its own key. Waiting events still count toward the queue limits. A partition function that throws or returns a value other than a string or undefined is reported as a failure of that event's handler, which then does not run -
7733b23: Add
gateway,.ignored Events gateway.flagsandgateway.presence, sent in each new session's Identify. TheignoredEventsoption lists dispatch names Fluxer should not send, or"auto"to suppress every type no registered event, enabled cache category or SDK feature needs, computed at each new session. A handler registered after a session started receives suppressed types only from that shard's next new session, and arawsubscriber disables automatic suppression. Settingflags: { debounceturns on Fluxer'sMessage Reactions: true } DEBOUNCE_flag, merging direct-message reaction runs intoMESSAGE_ REACTIONS message. TheReaction Add Many presenceoption sets the initial presence, which Identify carries and the SDK publishes after READY as ifpresence.sethad been called before connecting. An explicitignoredEventslist that names a type the SDK or an enabled cache needs fails client creation withConfigurationError.GatewayOptionsis a new public type -
7733b23: Expose the instance's advertised web domain migration as
domainMigrationonResolvedInstance, typed as the newInstance, and log an Info record when a migration is enabledDomain Migration -
314ac29: Add
client, which changes a running client's log level and per-category levels with the same validation as the creation settings. Omitted settings return to their defaults, and the.logging .configure( { level, categories }) debugsetting andFLUXERLY_DEBUGstill apply. The Effect API has the same member, because its clients apply these levels before records reach the Effect logger and the two clients keep the same members. The newLogLevelSettingstype describes the accepted settings -
abb5d3f:
messages.delete,messages.deleteMany,messages.pinandmessages.unpinaccept anauditReasonoption in both entry points, which Fluxer now records in the community's audit log -
7733b23: Message operations accept a plain string as the whole message and an
EmbedBuilderwherever they accept embeds, in both entry points. A string such asclientis shorthand for.messages .send( channel Id, "Hello") { content: "Hello" }inmessages.send,messages.reply,messages.edit,directMessages.sendand a webhook client'ssendandeditMessage. AnEmbedBuilderinembedsis built once when the operation reads its input, soclientneeds no.messages .reply( message, { embeds: [builders .embed() .title( "Status")] }) build()call and later changes to the builder do not affect that operation -
7733b23: Send text-to-speech messages with
tts: trueonmessages.send,messages.reply,directMessages.sendand therunBotreply helpers. A non-boolean value fails with reasoninputbefore any request. In a community, Fluxer sends a normal message without an error when the bot lacks the Send TTS Messages permission. Webhook messages do not accepttts, because Fluxer never sends a webhook message as text-to-speech. Webhook sends, replies and forwards withttsfail with reasoninputbefore any request. Messages gain an optionalttsfield, selectable throughmessageFields. Fluxer does not store the value, so it is meaningful only on the message returned by a send and onmessageCreateevents. Fetches, history pages and other later reads always reportfalse. A message whosettsis present but not a boolean is treated as malformed, like other malformed message fields -
7733b23: Add
MessageTypeconstants to both entry points, naming the values ofMessage.type, for examplemessage. The constants are.type === Message Type .User Join Default,RecipientAdd,RecipientRemove,Call,ChannelNameChange,ChannelIconChange,ChannelPinnedMessage,UserJoin,ChannelFollowAdd,ThreadCreated,ReplyandThreadStarterMessage.Messagestays one shape rather than a union by type, because Fluxer returns the same message fields for every type. A value Fluxer adds later is still kept inMessage.type -
d3a22bc: A missing-permission rejection now names the permissions the failed operation needs, in both entry points. When Fluxer answers HTTP
403withMISSING_PERMISSIONS, guild, channel, message, send and webhook operation errors list them indetailsas.required Permissions Permissionskeys, such as["View, and the hint names them, as in "The bot needs View Channel and Manage Channels for this operation". The hint also says where to grant them and adds other checks behind the same rejection, such as role hierarchy for moderation or Manage Roles when a channel edit changes permission overwrites. Fluxer does not report which permission is missing, so the bot may already have some of the listed ones. The change covers 72 operations, including channel, webhook, invite, role, member moderation, message, pin and reaction operations. Operations whose checks depend on data the SDK does not have, such as editing or deleting an emoji or sticker, andChannel", "Manage Channels"] rest.requestkeep a general hint withoutdetails. Operations without an entry that previously received the generic message-sending or moderation hint now receive the general hint instead.required Permissions -
7ec55f6:
PresenceUpdate, and so each entry ofPresenceUpdateBulkand of the presences in a member chunk, now carriescustomStatusin both entry points, instead of dropping the custom status Fluxer sends. The value isnullwhen the account has none, which includes an offline or invisible account because Fluxer hides the status of one, and otherwise anObservedCustomStatuswithtext,emojiandexpiresAt. Theemojifield isnullor anObservedwithCustom Status Emoji id,nameandanimated, so a custom emoji keeps its ID and animation flag and a Unicode emoji has a name and no ID and always reads asanimated: false. Fluxer stores custom statuses without validating them, so a field of the wrong type reads asnulland the presence is still delivered -
7733b23: Add the
rawevent, which delivers every gateway dispatch a shard accepts as{ shardId, t, s, d }, includingREADY,RESUMEDand types this SDK version does not decode. The body is Fluxer's unvalidated wire data and is not part of the SDK's compatibility contract. Delivery is logged at Trace with codeevents.raw, and nothing is built while norawsubscriber exists. Types listed ingatewaynever arrive.ignored Events Add
clientfor gateway commands without an SDK method. It fails with.gateway .send( shard Id, op, d) GatewaySendErrorwhosereasonisinputfor invalid arguments or frames above Fluxer's 4,096-byte limit,reservedfor heartbeat, Identify, Resume and server-only opcodes,notOwned,notReadyorbusy, and withClientClosedErrorafter shutdown. A throw from a getter ortoJSONin the command data rejects withSdkDefectcodeapplication.defect, and cycles, BigInt values and overly deep data fail with reasoninput. Completion means the frame was handed to the connection. Each sent command is logged at Debug with codegateway.commandSent -
34b3ab0: Reaction pages show several pages in one message that people flip with ◀ and ▶ reactions, because Fluxer has no buttons. Call
ctx.paginate(pages)in a prefix command, where only the command's author can flip by default, orclientelsewhere, in both the default and Effect APIs. Pages are strings or.messages .paginate( channel Id, pages, { users }) { content, embeds }objects. Adding or removing an arrow both count as a click, so the bot needs only Add Reactions and Read Message History, andremoveClicks: trueinstead removes each click, which needs Manage Messages. Listening stops afteridleMs(default 60,000) without a click and at most aftertimeoutMs(default 300,000), then the bot removes its own arrows and the current page stays. The result reportsidle,timeoutorsinglePage, and failures and cancellation use the usual failure channel. The newctx.sendHelp()sends the router's help as one message, or as reaction pages when it needs more than one page. So that pages started after connecting receive clicks,gatewaynow always keeps.ignored Events: "auto" MESSAGE_REACTION_ADDandMESSAGE_REACTION_ REMOVE -
7733b23: Export the types that public signatures already referenced, so they can be named in application code and appear in the reference. Both entry points now export
FormatHelpers,SnowflakeHelpers,DisplayHelpers,PermissionBitHelpers,ColorHelpers,TextHelpers,LinkHelpersandAssetHelpersfor the helper objects, whosetryParsemethods return a Result in the default entry point and an Effect in the Effect entry point, andOperation,MessageContent,EditMessageOptions,Webhook,Message Options AuditLogFilter,AuditLogQueryBase,Audit,Log Iteration Query Base CommandandArgument Optional Value ErrorMatchHandlers. Both entry points addCommandsandPrefixCommandBatch, each with its own API's shape, and the Effect entry point addsBatchRequirements,CommandRequirements,EffectRequirements,BotEventServicesandHandlerServicesEvery public export now has a reference category, and each client method has one description shared by both entry points, with the differences between them stated separately
-
7755411: Every resource cache kind now accepts a
maxAgeMscallback, as themessagescache already did, so an application can choose per community, channel or other snapshot what gets cached. The callback receives the frozen snapshot and returns a duration in milliseconds,nullfor no age limit or0to skip retention, for examplechannels: { max. A callback that throws or returns an invalid value removes the older copy and is reported as aAge Ms: ( channel) => ( watched .has( channel .guild Id) ? null : 0) } cachefailure to the client-levelonError, or logged at Error, without changing the event or REST result.Resourcenow takes the snapshot type as a type parameter, andCache Settings ClientOptions.cachepasses the matching type for each kind -
7733b23: Add
clientfor Fluxer API routes without an SDK method. Default calls return a.rest .request( { method, path, query?, body?, files?, audit Reason?, timeout Ms? }) ResultAsyncand acceptsignal, and Effect calls return an Effect. Both resolve with{ status, headers, body }, where header names are lower case andbodyis the parsed JSON body orundefinedfor an empty response. Requests use the client's credential, queue, learned rate-limit buckets, 429 waits, deadline, logging and bounded GET retries. Other methods are sent again only after a confirmed 429, never after an uncertain outcome. Paths must be relative API paths such as/users/@me, without a scheme, host, query, fragment, dot segments or the/v1prefix. Token-authenticated webhook routes are rejected because their credential is part of the path. Invalid input fails withRestRequestErrorreasoninputbefore any request, a non-2xx status fails withRestRequestErrorcarrying the status and sanitizedapiError, and a request after shutdown fails withClientClosedError. Logs, metrics and spans show only a route template in which IDs, codes and other non-word segments are placeholders. Thefilesfield accepts the message attachment inputs and sends them inline as multipart form data with their metadata inpayload_json -
7733b23: Add the
restclient option to tune REST scheduling: Theconcurrencysetting for Fluxer API requests that run at once (1 through 64),mediaConcurrencyfor attachment downloads (1 through 64, default 4),maxQueued(1 through 65,536, default 256),queuedJsonMaxBytes(64 KiB through 256 MiB, default 4 MiB) anddefaultTimeoutMs(default 30,000). By default a client runs 4 API requests at once for each of its shards, up to 64, instead of 4 in total, and the limit follows the shard plan whensharding: "auto"sizes or enlarges it. An explicitconcurrencyis used as given. ThedefaultTimeoutMssetting replaces the fixed 30,000 ms deadline of REST operations and attachment downloads that omittimeoutMs. Invalid values throwConfigurationErrornaming the field in the default API and are defects in the Effect API, andclientreports the configured capacities.diagnostics() .rest A request that fails with reason
busybecause the REST queue or the upload byte budget is full now logsrest.busyat Warn, at most once a minute, naming the full limit. Every such failure is counted in the newdiagnostics()counter.counters .rest Busy -
7733b23: A shard that resumes a session from the
sharding.sessionsstore now refills the enabled community, role and channel caches through REST, because a resumed session receives no community data. Fluxer calls a community a guild. Once every shard is ready, the SDK lists the bot's communities and fetches each one on a resumed shard, one request at a time, so application requests keep the other REST slots and rate limits apply as usual. The refill is logged with codelifecycle, at Info when it succeeds and at Warn when some communities fail or it stops after 10 failures.cache Refill Set
shardingto.refill Caches falseto skip the refill -
37612fd: The
runBotcommandsoption now accepts the delivery settings of the router'sattachin both entry points:concurrency,partition,overflow,maxPendingMessagesandmaxPendingBytes. They reach the router unchanged and are checked before any client exists, so an invalid value throwsConfigurationErrorin the default API and dies with it in the Effect API. PreviouslyrunBotpassed onlyonError, so its commands always ran eight at a time and dropped the oldest waiting message when 256 were waiting. A command router set tooverflow: "stop"stops the bot when its queue fills, like a handler. The commands guide shows the settings -
dd52acd: The default API's
runBotpasses itssetupcallback a second argument,{ signal }. TheAbortSignalaborts when the bot begins stopping for any reason, including a requested stop before its drain, a failure and a stop while setup is still running. Pass it to SDK operations, or clear timers and end other background work started insetupwhen it aborts, so that work ends with the bot. The runner does not track or await such work. In the defaultcreateTestBot, the signal aborts when the test bot's shutdown startsThe Effect API's
setupis unchanged. It already runs in the bot's Scope, so its finalizers run and fibers it forks into that Scope are interrupted when the bot stops -
7733b23: Add scaling options for large bots. The setting
sharding: "auto", ortotalShards: "auto", counts the bot's communities at the first connect and uses one shard per 2,000 communities. Fluxer calls a community a guild. Fluxer rejects a new session with close code4011(sharding required) when one shard holds more than 2,500 communities, and community IDs do not split evenly across shards, so the lower target keeps every shard below that ceiling and leaves room to grow. Counting the communities has its ownconnectiondeadline, so a slow count does not shorten the time the shards get to connect. Until the count completes,.startup Timeout Ms client.shardsis empty. Automatic sizing assigns every shard to the client, so processes that split the shards need an explicit total. When the bot outgrows its plan while the client runs and Fluxer closes a shard with close code4011, the client keeps running. The SDK counts the communities again and moves every shard to the plan for the new count, or to one more shard when the count still fits the old total. Every shard then starts a new session under the new total, so events sent during the move are missed, and the community caches refill from the new sessions. The move is logged once at Warn with codelifecycle.reshardedand the old and new totals. After 3 moves within an hour, or at 16,384 shards, a further4011closure ends the client with aConnectionError, and thelifecyclerecord says why. An explicit.connection Ended totalShardsstill ends the client on the first4011closure. An application can passshardingto coordinate new-session Identify commands across processes, and a refused permit is logged at Error and retried like a connection failure. The coordinator owns Identify pacing, so the SDK adds no spacing of its own before a permit. A coordinator should space its permits, for example by one second, to stay within Fluxer's budget of 300 Identify commands per source IP address in each 60-second window. A.identify .permit( shard Id, total Shards, signal) sharding.sessionsstore loads a saved{ sessionsnapshot at startup and saves one at shutdown, so a restart within Fluxer's 60-second window resumes instead of starting a new session. Stale, malformed or foreign snapshots, snapshots saved under a different shard total and store failures are logged and never fail startup. TheId, sequence, resume Url, saved At, total Shards } connection.recoveryoption setsminDelayMs,maxDelayMs,attemptTimeoutMsandhealthyResetMsfor established-session recovery, with the previous 1,000, 30,000, 30,000 and 60,000 ms as defaults, and the Effect API also accepts aschedulethat decides recovery delays and ends recovery when it completes.Connection,Recovery Options IdentifyCoordinator,SessionStoreandSessionSnapshotare new public types -
ce65b67: Events that shutdown or a closed subscription discards are now logged instead of disappearing without a trace. When the client closes, each subscription that still had waiting events logs one
events.discardedInfo record with the count infields.discarded. Closing a subscription with waiting events logs the same record at Debug. A draining shutdown that received events for open subscriptions or collectors logs oneevents.refusedInfo record with the count infields.refused. All of these events are also counted in the newdiagnostics()counter. A subscription stopped by.counters .events Dropped .closed overflow: "stop"keeps its existingevents.overflowrecord -
ada1d09: Add an optional
runBotrunner to both SDK entry points for critical subscriptions, graceful shutdown and opt-in process signal handlingConfigure event handlers in one object, with automatic subscription registration, access to the full client and a message reply helper. Keep the low-level APIs for custom lifetime and subscription control
Keep native Effect context and Cause information while the default API returns expected failures as ResultAsync and rejects with
SdkDefectfor defects -
7733b23: The
tokenoption ofsupervisor.child.runacceptsstring | undefinedin both entry points, ascreateClientandrunBotdo, soprocesscan be passed directly. A missing or blank token still fails.env .FLUXER_ BOT_ TOKEN child.runwithConfigurationErroronce the assignment arrives, and the parent then sees that child fail during configuration -
7733b23: Add scaling options to the supervisor in both APIs. As an alternative to hand-written
assignments, the newprocessesandshardsPerProcessoptions split every shard into contiguous blocks, one per child, namedprocess-0,process-1and so on. WithtotalShards: "auto"the supervisor counts the communities the bot is in, which the API calls guilds, when it starts and uses one shard per 2,000 communities, as a client withsharding: "auto"does. The count needs the newtokensupervisor option and acceptsinstanceandtransportfor a self-hosted instance or a proxy. It logssupervisor, and a failed count fails.automatic Sharding startwith the newSupervisorErrorreasonshardCount, whose cause is the count's error. When Fluxer later closes a child's shard with close code4011because the bot outgrew the plan, the supervisor stops every child, counts again and starts children for a larger plan, logging asupervisor.reshardedWarn. After 3 such moves within an hour, or at 16,384 shards, it fails with reasonclosedinstead. Theidentify.coordinatorsupervisor option accepts the sameIdentifyCoordinatora client accepts insharding.identify, so supervisors on several hosts can share Fluxer's Identify budget. The parent asks it before every grant and adds no spacing of its own, and a refused permit is logged at Error with codesupervisorwhile the child retries like after a network failure. Supervised children can keep resumable sessions across a deploy with.identify Permit Failed clientinOptions .sharding .sessions supervisor.child.run. A shard plan or identify coordinator in thatshardingobject is rejected withConfigurationError, because the parent assigns shards and paces Identify. Each child now sends its client diagnostics (gateway latency, shard states, REST queue, cache use and counters) to the parent when it finishes configuration and then everydiagnostics, 5,000 ms by default. TheInterval Ms status()method shows the latest snapshot in the newdiagnosticsfield of each child.SupervisorandChild Diagnostics Supervisorare new public types.Child Sharding ConfigurationErrorcan name the new fieldsprocesses,shardsPerProcessanddiagnostics, and code that switches over everyInterval Ms SupervisorErrorreason needs a case forshardCount -
314ac29: Add
say(to the test bot of both testing entry points. It delivers a message from a human user, waits until the bot settles ascontent, options?) idledoes, and returns the messages the bot sent in response in wire shape, or an empty list when it sent nothing. The defaultcreateTestBotnow returns the newTestBottype, and the newTestSayOptionstype adds message overrides to the wait options -
7733b23: Add
@neontechspace/andfluxerly/ testing @neontechspace/for application tests that need no network, token or Fluxer account. Thefluxerly/ effect/ testing createTestClientfunction returns a real client wired through thetransportoption to an in-memory Fluxer, together with controls: Theready()control connects through a protocol-v1 fake gateway,emit(delivers a wire dispatch such astype, payload, { shard Id }) MESSAGE_CREATEthrough the SDK's own decoders, caches and handlers, anddisconnect(closes a shard's connection so the client resumes, or stops for a fatal close code. The{ shard Id, code }) userandheartbeatIntervalMssettings choose the bot account reported in READY and the HELLO heartbeat interval. Therestcontrol answers HTTP requests matched by a.respond( matcher, response | handler) "METHOD /pattern, a RegExp or apath/:param" { method, path }object,requests()andcommands()record what the client sent without its token or Authorization header,logs()captures log records at the configured level while still calling application sinks, andcounters()reads the diagnostics counters. Message sends and edits without a matchingrest.respondregistration receive an echoed bot message. Other unmatched requests receive a Fluxer-shaped 404 and atestingWarn record. Register an explicit failure response to test a rejected message send or edit. The native.unmatched Request createTestClientis scoped: Closing its Scope shuts the client down and closes the transport, while the default client usesshutdown()orawait using. Both entry points sharefixtures,createFixturesandfixtureToken: Deterministic snake_case wire builders for users, the bot user, communities, GUILD_CREATE bodies, channels, roles, members and messages whose defaults refer to one community, channel and authorThe
createTestBotfunction tests a bot written forrunBotas written. It takes the bot's own options object, registers itseventsandcommandswith the same handler contexts, delivery defaults and command router asrunBot, and runssetuponce inreadybefore connecting. Thesignal,processSignals,reportFailureanddrainMssettings are accepted and ignored, and an undefinedtokenuses the fixture token. A misspelled or unsupported option key throwsConfigurationErrorwith a hint naming the closest key thatcreateTestClientorcreateTestBotaccepts, ascreateClientdoes. A failed setup rejectsreadywithApplicationError. The result is the usual test client, typedTestBotin the Effect entry pointTests wait without building their own promises or sleeping. The
TestRoute.next()control returns the next request a registered response answered, in order, including one that arrived before the call. Theidle()control waits until no handler or command is running and no request is pending, which confirms that the bot did nothing after an emitted event. Both fail withTestTimeoutErroraftertimeoutMswhen it is passed and otherwise wait without a deadline, and the native entry point returns them as Effects. Their timers stay real when a test fakes timers, for example withvi.useFakeTimers(). The SDK runs onsetImmediate, so fake timers must leave it real, andidle()rejects withConfigurationError, or dies with it in the native entry point, instead of settling while no handler can runA test client fails the test when application code failed without a handler, such as an event handler or command that threw, or returned a failed Result such as a rejected reply, while no
onErrorhook was registered. The defaultshutdown()andawait usingdisposal reject withUnhandled, which lists the Error records of those failures. The nativeTest Failures Error shutdown()fails with it, and closing the scope that created the test client dies with it. Each failure is reported once, so a later shutdown succeeds. A test that makes application code fail on purpose reads the failure from thefailures()control, which marks it as expected, or handles it withonError -
7733b23: Add the advanced
transportclient option. ItsfetchandwebSocketsettings replace the HTTP and WebSocket implementations, for example for proxies, instrumentation or tests, anduserAgentreplaces the User-Agent header. Replacements receive every client request, including instance discovery, REST, signed upload parts, attachment downloads and gateway connections. Invalid values throwConfigurationErrornaming the field in the default API and are defects in the Effect API. Bot, webhook and OAuth clients now sendUser-Agent: Fluxerlywith every HTTP request and gateway handshake. A configured.js/<version> ( +https:// fluxerly .neontechspace .com) transport.userAgentreplaces it for a bot client. Custom WebSocket factories receive the handshake headers, including the User-Agent, inWebSocket Options .headers -
1eeb268: A client created without
shardingsettings now keeps running when the bot outgrows one shard. Fluxer refuses a new session with close code4011(sharding required) once one shard would hold more than 2,500 communities, and such a client previously ended with aConnectionError, so a restart or a lost session took the bot down until its configuration changed. The client now counts the bot's communities and moves to a larger plan assharding: "auto"does, with the same limit of 3 moves within an hour, and logslifecycle.reshardedat Warn with the old and new totals. Every shard starts a new session for the move, so events sent during it are missed. While the bot stays above the limit, each start first opens a session that Fluxer refuses, whichsharding: "auto"avoids. An explicittotalShards, including 1, still ends the client on4011, and supervised children are unchanged because the supervisor always assigns an explicit plan -
dd52acd: Add
clientto both entry points, a local lookup of the bot's own public account as the gateway's READY reported it, without a request. It returns the.users .get Self() Userdirectly in the default API and a never-failing Effect in the Effect API, likeusers.get. The result is undefined until the client's first READY, and also while no READY has carried a complete account. A later READY replaces it only when it carries a complete account, and the value remains available after the client stops. Other user updates do not change it, so useusers.fetchSelffor a current remote snapshot -
2b86017:
MessageFlagsgainsVoiceMessage(8192), which marks a voice message in Fluxer. Message and webhook send and edit operations still reject this bit before dispatch, because onlySuppressEmbedsandSuppressare writable in this SDKNotifications -
a4c3572:
VoiceState, whichvoiceStateUpdateandvoiceStateSnapshotdeliver, now reportsisSelfVideoOn,isSelfStreaming,viewerStreamKeysandmember. The first three also appear onCallVoiceState, whilememberdoes not, because Fluxer attaches no community member in a private call. Thememberfield is the sameGuildMembershape that member events use and is omitted when Fluxer sends none. An omitted video or stream flag reads as false and omitted watched streams read as an empty list, so events from servers that do not send them still arrive. A voice state whose member data is present but malformed is rejected whole, like any other malformed field -
abb5d3f: Webhook message sends, replies and forwards accept an optional
noncein both entry points. For five minutes, Fluxer then tries to suppress another send from the same webhook with the same nonce, which helps after a lost response but does not guarantee exactly-once delivery. Webhook sends without a nonce send none, and a forward that sets a nonce both on the message and on its source fails before the request
Patch Changes
-
86695d5: The shipped agent guide in
agents/AGENTS.mdnow 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 filedist/index.d.ts. 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 sectionfluxerly agentswrites names the installed package's manifest as the place to compare versions and mentions the task map -
d67c5b3: 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
rest.concurrencyandrestoptions 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 users.fetchSelfanddirectMessages.opencomments explain when a result is not cached, and theusers.fetchanddirectMessages.fetchcomments state the 256-ID exception -
5e98527: A native
on,messages.collectormessagesregistered 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 -
a89dc6d: 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
-
38d41bf: 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 -
8b70a5c: The missing-permission hint for
messages.addReactionnow 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 -
c44e7bc: 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 -
0f8a442: The
.envfile thatfluxerly initcreates for the bot token is now readable only by its owner on Linux andmacOS, with mode 0600, so other local accounts cannot read the token. An existing.envkeeps its content and permissions -
408fafb: 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. Thetext.truncateandtext.splithelpers andtoWellFormed()clean such text, andclient.rest.requeststill sends its body as given -
051a969: The
members.movecomments 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 reasonrejected, HTTP400anddetails.providerCodeUSER_NOT_IN_VOICE, even though the member is in voice. A retry fails the same way while the destination stays in peer-to-peer mode -
e831234: The Effect API
supervisor.child.runhelper now succeeds when the parent stops the child before its gateway session is ready, as the default API helper already did. It previously failed withClientClosedErrorafter that normal stop -
a89dc6d: 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.
-
8e2a652: 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. -
08747b6: The
rest.rejectedWarn record logged for a refused permission overwrite write now carries a precisefields.hint. Before, a 403MISSING_PERMISSIONSon setting or removing a channel's permission overwrite logged the general missing-permission hint. Now the hint is the one thatchannelsand.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 -
e5e7d10: Fixes for the guild cache, command replies, retry classification and message cleanup. A successful
discovery.apply,discovery.editorguilds.editVanityUrlcall no longer stores its reply as a cached guild, soguilds.getand guild enumeration no longer return an application or vanity object after one of these writes. These writes now only remove the cached guildThe
MessageCleanupErrorfrom apreviewwhosefilterthrew now keeps the thrown value ascause, as documentedWith
mentionPrefix, help pages and the usage line of a rejection reply now put one space between the bot mention and the command name, such as<@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
errors.isRetryablefunction now returnsfalsefor aConnectionErrorwith reasonnetworkand 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
client.observeStatenow throwsConfigurationErrorfor a listener that is not a function, instead of logging an observer failure on every state changeThe
Commandcomments no longer giveArgument Metadata user, which registration rejects, as a type example, and say that a custom argument can also userest -
e5e7d10: A session saved by
sharding.sessionsis 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 emptysessionId. A later load of that copy starts a new session and logslifecycleat 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" -
3dc3081: An explicit
client.shutdown()of 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 indiagnostics(). Its final record previously said the shutdown completed and the counter stayed at 0.counters .cleanup Failures -
3dc3081: When
client.shutdown()cancels 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 -
a89dc6d: The package's documentation links, the agent guide and the
initcommand's guide link now point to the website athttps://.fluxerly .neontechspace .com -
963f0be: The comments on
GuildBan.moderatorIdandAuditLogEntry.userIdnow state that the 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 aGuildUpdateentry. Both IDs already decoded, so no behavior changes -
e5e7d10: With the default
childOutputofprefix, 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 afields.childentry on ajsonstream. For this, default-API children always logjson, as do Effect API children whoselogging.formatisjson. SettingFLUXERLY_LOG_FORMATorFLUXERLY_LOG_COLORin the child environment still takes precedenceBefore, 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 fields.childentry. Those records now keep their shape and gainfields.child, while standard output still shows readable lines -
e831234: The supervisor no longer fails with a
SupervisorErrorof reasonspawnwhen 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 theshutdownTimeoutMsdeadline, and then handles the exit as usual, in both APIs -
1d54b09: The supervisor
waitForClosemethod 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 aSupervisorErrorof reasonspawn. The shutdown completes once the child exits, or at theshutdownTimeoutMsdeadline for a child that is still running, in both APIs -
99bf06d: 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. Deliveringfixtures.messagethroughemitstill passes content unchanged -
f1aebe9: The coding agent guide's list of APIs that Fluxerly does not have, such as
client.loginand message methods, now stands on its own without naming another library. Its rules section, whichfluxerly agentscopies into an application'sAGENTS.md, keeps the instruction to use only declared methods. Run the command again to refresh an existing section -
6655b27: The
AGENTS.mdsection thatfluxerly agentswrites now recordsnpx --no fluxerly agentsorbunx --no-install fluxerly agentsas its refresh command, and pnpm projects keeppnpm exec fluxerly agents. These forms never install a package automatically, because the unscopedfluxerlyname on npm is not this SDK, so the SDK must already be installed in the project. Run the command again to update an existing section -
abb5d3f: API comments now explain recent Fluxer behavior. Member search can fail with
twoFactorRequiredin a community that requires two-factor authentication for moderators, even without filters. The oldernsfw: falsechannel field restores inheritance on create and edit, andnsfwOverride: falsemarks a channel as not adult-only. Message search in an age-restricted community can include adult content even whenincludeNsfwis false. Member queries over the gateway match the start of a nickname, global name or username. A rejected bot token or OAuth grant can come from the owning account's standing, which a new token does not fix -
7733b23: TypeScript now reports a mistake in one argument descriptor of
registerManyor the keyedcommandsobject ofrunBot, such as{ type: "int" }, on that descriptor with the list of valid types, and the other commands keep their inferredvalues. Previously the error named the whole schema and every command lost its value types -
1afd073: Reject malformed array entries using their indexed values while preserving documented local bounds
Reject impossible calendar dates in embed timestamps, pin cursors, guild history cutoffs and status expiry before dispatch or retention. Apply the same calendar validation to received message, pin, channel, guild, member, invite, ban and discovery timestamps
-
1afd073: Expose Fluxer's recorded positive member-ban message-deletion duration through audit-log REST reads and gateway events
-
1afd073: Honor inherited and non-enumerable audit-log iterator bounds, preserving requested cursors and enforcing page limits through both APIs
-
1afd073: Keep malformed nested allowed-mention selections and embed values other than the converted color and timestamp intact when building message inputs, so message-operation validation rejects them before a request starts
-
1afd073: Preserve inherited and non-enumerable supported structural properties when builders snapshot embeds, attachments, message references and allowed mentions. Builders retain unknown own enumerable fields for message-operation validation and attachment source references
-
f0e7941: The
CacheChangeexample now tracks cached keys, so replaced entries and cleared kinds stay accurate. TheResourcedescription now names the one preload, the refill after a shard resumes a stored session, whichCache Settings shardingcontrols.refill Caches -
7733b23: Expire entries in every cache category through an expiry heap and reschedule the expiry timer only when the next deadline changes, so timed cache writes no longer scan every retained entry
-
7733b23: A caller AbortSignal whose
addEventListenerthrows no longer leaves therunBotSIGINT and SIGTERM listeners installed. TherunBotfunction now registers the caller signal listener before its process signal listeners and removes it after them -
139f4dc: Include standalone JavaScript, TypeScript and native Effect bot starters using the public SDK runner for critical subscriptions and awaited cleanup
Clarify ordinary handler Promise ownership and add checked-JavaScript consumer coverage and symptom-based troubleshooting using existing diagnostics
-
1afd073: Accept voice-channel bitrate requests through 384,000 bits per second and return the value applied by Fluxer
-
5f6b6dd: Refresh the package README with matching JavaScript, TypeScript and Effect starter setup, explicit lifecycle ownership and installed Effect version guidance
-
d19d3c7: Prefix commands now run only for messages a person typed, meaning the
DefaultandReplymessage types, and skip every other type. When a thread is created, Fluxer posts a notice of type 18 into the parent channel, with the thread's name as its content and the thread's creator as its author, so a thread named!pingran the!pingcommand as its creator. The messagetypeis now part ofMessageCore, so every client keeps it, even with amessageFieldsselection that leaves it out. A message without atype, such as a hand-built test message, still counts as typed -
ce65b67: Reading the bot's community list page by page no longer repeats one page when Fluxer returns a page that does not move past the previous one. Automatic sharding now fails at once with a discovery
ConnectionErrorthat says so, and the supervisor's count fortotalShards: "auto"reports that error as the cause of itsshardCountfailure. The count previously requested the same full page again untilconnectionpassed and then failed with a.startup Timeout Ms Connection. The cache refill after restored sessions now stops at such a page and logsTimeout Error lifecycleat Warn, where it previously requested the same page until the client closed without logging the refill.cache Refill -
ce65b67: Log deduplication now keeps records about different shards or subscriptions apart. The same malformed dispatch on two shards previously collapsed into one record within the dedupe window, so the second shard's fault appeared only as a repeat count with the first shard's ID
-
2b9efdf: Cancelling a connect while a failed discovery response is being cleaned up again keeps the discovery
ConnectionErroralongside the interruption and the cleanup defect. The native Cause holds all three, and the default API'sSdkDefectlists them as reasons. With Effect 4.0.1, the discovery failure had been dropped -
7733b23: The
display.namehelper accepts a message author directly. Its user parameter now allows an absentdisplayName, as on received message authors, and falls back to the username -
3c685e3: The deploying guide and the agent guide now say which
runBothandles SIGINT and SIGTERM by default. The default API does unlessprocessSignalsisfalse, while the native EffectrunBothandles them only withprocessSignals: true, because an Effect launcher such asNodeRuntime.runMainalready interrupts the program on both signals and an interrupted program skips the drain. Runtime behavior is unchanged -
37612fd: The comment on the Effect API's
BotOptionsno longer calls a missing or blank token misuse that dies withConfigurationError. AsrunBotalready behaved and documented, the bot fails with a typedConfigurationErroronce the other options are valid -
7733b23: Start faster by importing each Effect module the SDK uses from its own
effect/<Module>subpath instead of theeffectpackage index, which loads every Effect module. Importing@neontechspace/now loads 87 Effect modules instead of 203, andfluxerly @neontechspace/loads 133fluxerly/ effect -
abb5d3f: Embed media can reference any image or video attachment through
attachment://, following Fluxer's file-type detection, instead of only PNG, JPEG, WebP and GIF files, in both entry points -
7733b23: Loading the package with
require()now fails with an error explaining that the SDK is ESM-only and how to switch the project toimport -
1afd073: Identify affected generic event subscriptions in safe fallback diagnostics
-
c3bbcf9: Cache kinds with a
maxAgeMsexpiry now release a replaced or removed snapshot as soon as the cache drops it. Previously the expiry schedule kept such snapshots in memory until their original deadline came up or enough replacements accumulated to rebuild the schedule, so frequently updated entries of every cache kind, frommessagestoemojisandstickers, could hold more memory than the cache limits suggested -
abb5d3f: A successful emoji or sticker metadata or source lookup no longer pauses unrelated requests such as message sends. Fluxer limits these lookups in one bucket per bot across all emojis or stickers, and the SDK now tracks them that way, so running out of that bucket still delays the next lookup of any emoji or sticker
-
7733b23: Pace presence, member-subscription, member-chunk, count and
gateway.sendcommands on each gateway connection to at most 500 in any 60-second window, sent in order, so they stay within Fluxer's budget of 600 payloads per connection instead of closing it with 4008. Heartbeats, Identify and Resume are never delayed behind them -
abb5d3f: Gateway member requests, presence member selection and member count requests now reject IDs above 9223372036854775807 before sending, in both entry points. Fluxer ignores such IDs, so a request could wait until its timeout, and an explicit member list made only of such IDs could turn into a request for all members
-
abb5d3f: When the SDK plans to resume a gateway session, it now closes the connection with code
4000instead of1000, in both entry points. Fluxer ends a session when the client closes with1000or 1001, so a reconnect after a missed heartbeat or a restart from aSessionStorehad to identify again and lost the events sent in between. A final shutdown without aSessionStorestill closes with1000 -
7733b23: Event handlers use much less CPU while a backlog waits for a free handler slot
-
3c685e3: The
fluxerly initcommand no longer refuses a folder that already has a.gitignore, such as the one the Create a bot guide has readers write before the quick start. It keeps that file and appendsnode_modulesand.envonly when a line is missing, leaving the rest and its line endings as they were, and a failed run still leaves the file untouched. Its next steps now link the guide that explains how to get the token and invite the bot to a community, and the quick start presentsinitbefore saving the token by hand. -
3c685e3: The TypeScript and Effect starters from
fluxerly initnow typecheck. Node.js runs a.tsfile by removing its types without checking them, so a type error in a generated project went unnoticed. Init now installs TypeScript 7, the version the SDK documents as required, together with the Node.js types, adds acheckscript that runs the compiler and prints the command to run it in its next steps, such asnpm run check,pnpm run checkorbun run check. The JavaScript starter is unchanged -
3a6b15b: Initial release of Fluxerly.js
-
06be6c0: The hosted
links.installationURL now starts at Fluxer's API authorization route,https://, which redirects to the hosted installation page. The previousapi .fluxer .app/ v1/ oauth2/ authorize https://URL showed a page-not-found error. Installation links for a discovered instance still open that instance's web app directlyfluxer .app/ oauth2/ authorize -
9df8363: Update the WebSocket gateway transport dependency from ws 8.21.3 to 8.22.0
-
25125b0: Log records, unsafe payload records and
describeErroroutput now hide the user information of a URL, such asuser:passwordinhttps://, in both APIs. A user name without a password is hidden too, because it can be a credential on its own. The scheme, host and path stay readable, and anuser:password@host/ path @in a path, query or fragment is left as it was -
c927087: A
membersrequest that would be the client's 13th member request in 11 seconds now fails at once with.iterate Chunks MemberChunkErrorreasonrateLimitand aretryAfterMswait, without being sent. Fluxer accepts 12 member requests per account in 10 seconds and drops the rest without an answer, so such a request previously waited out its whole timeout and failed with reasontimeout. The count covers one client, or the children of one supervisor together, and other processes with the same token are not counted. Member requests sent withgateway.sendcount toward the same window from the moment the socket takes them, but are never refused -
abb5d3f: With the users or direct-message cache enabled, a member update now removes that member's cached user and clears cached direct-message channels before handlers run, in both entry points. Fluxer reports username and other account changes to bots through member updates, so cached users and DM recipients could show the old identity. Automatic gateway event selection keeps member updates for these caches even without a
guildMemberUpdatehandler. Later reads fetch fresh snapshots, and snapshots already returned stay unchanged -
7733b23: Keep a sent message in the message cache when its gateway echo arrives before the HTTP response. Fluxer omits the community ID,
guild_id, from message HTTP responses, and the cache previously treated that difference as a conflict and dropped the message. Other differences between overlapping observations still remove the cached message -
7733b23: Read each message input field once when sending, replying, editing, forwarding, sending direct messages or executing webhooks, so the validated value is the one sent. A getter on
content,flags,embeds,stickerIds,messageReference, anallowedMentionsswitch or an attachment field can no longer pass validation with one value and send another. Other operations read caller arrays and options once too. Themessages.deleteMany,guilds.fetchCounts,channelsand.fetch Member Counts presence.setMembersmethods copy their IDs once and use the validated copy. Cache entry limits, event wait options, presence input, pagination, search, member chunk, message cleanup, permission, instance resolution and attachment download options, webhook client options, supervisor options and client cache and upload settings read each field once -
1afd073: Remove unsupported message-search cursor inputs and result cursors. Traverse numbered pages with a fixed page size, report the provider's page ceiling explicitly and reject results with missing or unrelated channel context
-
1afd073: Reject array-valued OAuth operation options before discovery or request dispatch
-
7733b23: Keep the original OAuth response cleanup failure when reader cancellation or release fails, as REST and discovery cleanup already do. The native Cause holds it as the defect, the default API's
SdkDefectholds it as its cause, and several failures arrive as oneAggregateError. Response bodies never become part of it -
1afd073: Preserve unexpected OAuth construction failures as defects attributed to
oauth.create, with configuration details kept private -
1afd073: Keep OAuth consent, code exchange and revocation requests aligned with the values that passed local validation
-
ae161e3: A page read whose items arrive out of ID order now fails with
detailsset to.response Field order, as message history already did. This coversmembers.fetchPage,guilds.fetchPage,messagesand.fetch Reaction Users auditLogs.fetchPage. Previously the error named the last field read from a valid item, such as1.deaf, which pointed at the wrong cause -
6563bf3: API comments now warn where Fluxer applies one operation as several separate writes. A failed
roles.deletecan leave the role in place after some members already lost it, and a failedchannels.deletecan leave a channel that already lost its invites, webhooks, attachments and messages. A failed overwrite change on a category, throughchannels.edit,channelsor.set Permission Overwrite channels, can leave some of its channels with the old overwrites, and a retry may not reach them. The.remove Permission Overwrite channels.reordercomment now says its error does not list the moves that finished, and the communities guide shows how to keep the failure while reading the actual order and parents. The announcement guide's publish helpers log the sent message's ID when publishing fails, and the guide notes that a failed publish can still leave the message published -
1afd073: Reject role and channel permission writes above Fluxer's signed 64-bit maximum while preserving unsigned 64-bit response values
-
1afd073: Prevent stale private-conversation snapshots from surviving overlapping direct-message mutations
-
ce65b67: A REST request that would wait behind a known rate-limit pause past its deadline now fails at once with reason
rateLimitand the remaining wait inretryAfterMs, and logsratelimit.deadline, at Warn for the first such request on a route each minute when the wait is a second or longer, and at Debug for every other such request. Previously it waited until its timeout and failed with reasontimeout, so a long global 429 held every later request on the client for its whole deadline without saying why. The pause itself still lasts as long as Fluxer asked and ends on time, so requests sent after it go out normally -
1afd073: Accept static and animated custom-emoji markup in reaction requests and collectors. Parsed custom emoji, guild emoji snapshots and received reaction emoji now share the same input normalization across both APIs, including Unicode event values. Custom collectors continue matching by emoji ID after a rename
-
7733b23: Return the received failure at once when a GET retry after a network error or HTTP
500,502,503or504could not start before the operation deadline, such as a longRetry-After. Such a read previously waited for the whole deadline and failed with reasontimeout -
1afd073: Validate bounded resource input arrays from one indexed snapshot before encoding or dispatching requests
-
ce65b67: A connection lost while a REST response body arrives now fails with reason
network, keeping the sanitized transport code as the error's cause, and a read such asmessages.fetchor a GET throughrest.requestretries it at most twice like any other network error. Previously the operation failed with reasonresponse, as if Fluxer had sent malformed data, logged a misleadingrestWarn and reported the raw transport error as an SDK defect, so the read was never retried. A write whose response is cut off still fails with outcome.response Rejected unknownand is never sent again -
1afd073: Preserve HTTP rejection status and cleanup defects when releasing an error-response reader fails, instead of reporting an ordinary network error
-
01caebf: Preserve inherited and non-enumerable command fields during batch registration in both APIs, so supplied guards, argument schemas, cooldowns and rejection callbacks cannot silently disappear
Snapshot recognized command and cooldown fields before validation and retain those same values, preserving atomic registration and preventing changing getters from replacing validated policies
-
4ce40a1: Release event handler slots and partition keys after a synchronous handler or middleware throw, including when middleware calls next. The failure is reported once and the invocation emits its failure observation without blocking later events
Keep guards returning false silent under automatic rejection feedback, including the
runBotreply default. Rejection counters, Debug records and customonRejectcallbacks still receive the denial, while guards returning an explicit deny reason retain their replyInclude the matched command's canonical name and reported failure in each command router observation, with one observation spanning event and command middleware. Interrupted commands report cancelled, and messages with no command match remain unnamed observations. Command failure metrics use the same name and command spans cover middleware, guards and execution
-
4ce40a1: Retain the gateway command pacing history across Resume within a running client, so reconnecting cannot restart a full send budget while Fluxer still counts the previous session's commands. A fresh Identify starts a new budget
Withdraw unsent community-count, channel-member-count and member-chunk requests when cancelled or timed out, preventing stale requests from being sent later. Bound internal waiting commands and report queue pressure through the existing busy failures
Release a cancelled, unsent
gateway.sendcommand's local queue capacity immediately, so replacement work can wait instead of failing as busy until the pacing window opens. Queued commands are still abandoned on disconnect rather than replayedCoalesce outgoing status changes to one unsent update per shard, keeping only the latest intent while the gateway send budget is full. Measure the four-second interval from actual transmission, and avoid retaining timers that already fired during scheduling
Replace queued member-presence selections with the latest IDs and withdraw an unsent selection when cleared. Start member-selection spacing at actual transmission, so pacing delays cannot flush obsolete selections or clears together
-
4ce40a1: Match a handler's failed HTTP
401or403request by error identity, including wrapped causes, rather than status and Fluxer code. Unknown codes no longer cause duplicate rejection logs, and a handled request keeps its Warn when a different request with the same code fails the handler. A rejection is logged once unless the handler fails more than one second later, after its Warn has already appearedKeep credential-key values masked in unsafe REST payload logging when a response exceeds the 65,536-byte read limit, including passwords and cookies in truncated JSON text. Invite-shaped objects in truncated lists also keep their access codes masked, while unrelated diagnostic and provider codes stay readable
Mask credential patterns in application error names and string codes in log records, failure descriptions,
describeErrorand native Effect log causes and annotations. SDK error JSON also masks names in summarized causes, without changing the original error or its causeList Debug for handled failure reports in the error and log code catalogue, alongside the existing unhandled failure levels
-
4ce40a1: Classify failures from OAuth identity, community and connection reads and token introspection as reads in their messages, hints and details, so
errors.isRetryablecan identify eligible transient failures. Code exchange, refresh and revoke calls with unknown outcomes remain unsafe to repeat, and OAuth requests are not retried automaticallyPreserve sanitized transport causes, including safe codes such as
ECONNRESET, when discovery failures become resource errors and during OAuth calls and buffered or streamed attachment downloads. Raw transport errors and their private text are not retainedRetain unknown uppercase Fluxer error codes in
details.providerCodefor HTTP429failures, including when the required wait exceeds the operation deadlineReject raw REST paths whose reserved
/v1prefix or token-authenticated webhook route names use percent-encoded unreserved characters. Keep route checks case-sensitive and do not decode reserved charactersCompare pin page ordering and pagination progress at full fractional timestamp precision, with timezone offsets normalized by instant. Equal timestamps still remain equal
Reject audit message-deletion durations whose milliseconds fall outside the safe integer range, rather than projecting an overflowed value such as
Infinity, and round other durations to whole milliseconds. The same validation applies to REST audit pages and gateway audit events -
4ce40a1: Clear a shard's cached communities and dependent resources when partial replay of a saved session falls back to Identify. Clear reads cached during the outage again at
READY, while retaining replayed observations after a successful ResumeMake the in-memory test gateway enforce the session's Identify event filtering, including message mention exceptions and filtering retained across Resume. Emitting a dispatch Fluxer would suppress now fails with
ConfigurationErrorinstead of making an unrealistic test passKeep
MESSAGE_REACTION_ADDavailable under automatic filtering when amessagehandler is registered, because Fluxer generates reaction batches only from unsuppressed additions. Test reaction batches use the same source filter, including when an explicit list ignores only the generated batch nameReaction Add Many -
4ce40a1: Effect supervised children let running handlers, queued handler events and REST requests finish within the parent's drain allowance before closing when the parent stops them, including a move to a larger automatic shard plan. Failure still stops running work without draining
Supervisor child-run options and default readiness-wait options reject unsupported keys before waiting for the parent or observing readiness. Misspelled keys include a suggested supported name, as do assignment and Identify options
Both
runBotentry points mask the normalized token in failure reports before a client exists, including tokens surrounded by whitespace and matching quotesEffect
runBotretains readable token, logging andreportFailuresettings when an option getter throws. A throwing getter no longer discards a configured log sink, token masking orreportFailure: false -
6eaecbf: Align request text validation with Fluxer's field-specific normalization and UTF-16 limits without rewriting accepted input
Preserve global rate-limit pauses when error bodies are unavailable and learn server rate-limit buckets with bounded, resource-aware tracking
Bound complete gateway messages before decoding, including fragmented frames, and reject invalid UTF-8 through the transport
Keep attachment reads responsive to cancellation and deadlines when sources repeatedly yield empty chunks
Use ordinary JavaScript and TypeScript extensions inside ESM package scopes while retaining the existing public package entry points
-
1afd073: Preserve native shutdown interruption and cleanup-defect reasons without exposing a connection failure through shutdown. Keep both classifications in the default API's
SdkDefectreasons -
7733b23: Ship JavaScript source maps that point to the package's
src/files instead of embedding a second copy of every source file, which reduces the installed size. Stack traces with--enable-source-maps, debuggers and go-to-source still resolve to the original TypeScript -
7733b23: Large sharded bots now start within the default startup deadline. When a client owns several shards and has no
sharding.identifycoordinator, the SDK spaces new-session Identify commands by one second, as before, and now adds one second to the startup deadline for each shard after the first, so this spacing alone never fails startup. An Info record with codelifecyclestates the extended deadline.startup Deadline -
7733b23: Keep the original thrown value of an attachment stream cleanup failure that no caller observed. Client shutdown reports up to 64 retained failures with their values, and later ones are counted without their values
-
95ca9f2: The children of a supervisor now share the Fluxer limits that apply to the whole bot account, in both APIs. When one child is rate-limited globally, the parent holds the other children's REST requests until the same time, so they no longer keep sending into the pause and collect more
429responses. Member requests from all children share the member request limit through the parent. A child that gets no answer from its parent within one second counts only its own member requests and logssupervisorat Warn once. Per-route rate limits stay with each child. A client's first REST request also no longer waits out a global pause while it discovers the Fluxer instance, so it fails at once when the pause outlasts its deadline.member Requests Local -
7733b23: In the default API, a
configurecallback ofsupervisor.child.runthat throws or rejects makeschild.runreject withSdkDefectcodeapplication.defect. Its cause is anApplicationErrorwith sourcesupervisor child configure, whose own cause is the original value -
1afd073: Preserve inherited and non-enumerable client settings in both supervisor child APIs, so an explicitly selected instance is not silently replaced by hosted Fluxer. Reject inherited token and sharding overrides as well as own-property overrides
-
ce65b67: The supervisor now logs why before it acts. A child that misses
startupTimeoutMslogssupervisorat Error before the supervisor shuts down. A child force-terminated after ignoring a stop request logs.startup Timeout supervisorat Warn, and one that lost its message channel and kept running logs it at Error before the supervisor shuts down. A child that does not confirm sending Identify within 5 seconds of its permission logs.terminated supervisorat Warn before it is stopped. A failed read of a child's output, which ends forwarding of that stream while the child keeps running, logs.identify Unacknowledged supervisorat Warn with the error. A failed message send to a child now keeps its error in the.output Failed supervisorrecord.spawn Failed -
0b7c49f: A supervisor with
totalShards: "auto"now completes its move to a larger plan when a child's shard asks to start a session, or reports one it just started, while every child stops for the move. Either message previously failed the whole supervisor with reasonprotocolinstead of moving. A request during the move stays unanswered until its stopping child withdraws it, and an Identify that a child sent after its permission still counts, so the larger plan's first Identify waitsidentifyafter it.minimum Spacing Ms -
01caebf: Treat runner-initiated client closure as normal when stopping a bot, while preserving run and cleanup failures
Preserve combined native failures and default-API
SdkDefectreasons when an operation and cleanup both fail -
37612fd: The test clients of both testing entry points now see every unhandled failure, whatever their logging settings. Previously
failures()and the shutdown check read failures from the captured log records, so a test client with a logging level aboveerroror a silenteventsorcommandscategory hid a broken handler, and shutdown passed. A failure repeated within the log deduplication window also appeared infailures()only once, and shutdown then rejected for the summary of the suppressed repeats. Each failure now appears infailures()once and counts for shutdown, whilelogs()still follows the logging settings -
0bca1c9: The
idle()andsaywaits of both testing entry points now also wait for received events whose handler has not started yet. Previously they counted only handlers that were running, so a wait could settle between two invocations while emitted events still waited in a handler's queue, and a test then asserted before its handler ran. This happened mostly with Effect API handlers, which run on the registering caller's scheduler -
1afd073: Capture message targets, search scopes and traversal bounds, pin and reaction-user cursors, and reaction emoji identities once before validation and dispatch, preventing later caller-controlled property reads from changing an accepted request
-
1afd073: Keep webhook create and edit requests aligned with the settings that passed local validation