On This Page

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 effect peer 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 an SdkDefect lists the original error as a Defect reason instead of a Failure reason, 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 a Die next to the cleanup defect, so catchTag no longer recovers from it. The original error is still the public one, such as MessageOperationError or ConnectionError, never an internal REST or gateway failure

    Migration: Update Effect to 4.1.0 or a later Effect 4 release. Code that read the Failure reason of an SdkDefect to find the original error should also look at its Defect reasons

  • 2c9dfde: Breaking change for code that builds ShardState objects by hand, such as test doubles of client.shards: The new sessionEpoch field is required. Add sessionEpoch: 0 to such objects. The same applies to code that builds EventContext objects by hand, such as tests that call handlers or middleware directly, which must now set sessionEpoch and receipt, and to code that builds BotEventContext objects by hand to call runBot handlers directly, which must now set context

  • 321d8bb: Breaking changes in commands.parseQuoted: A single quote is now an apostrophe unless it starts a word, and its rejections carry the command name. Double quotes and backslash escapes are unchanged, and the rejections for an unclosed quote and a trailing backslash change only as described below. A single quote opens a quoted span only at the start of the text, after whitespace or directly after a span that just closed. Inside a single-quoted span, a single quote ends the span unless the next character is a letter or digit, so the end of the text, whitespace, quotes and punctuation all end it. Every other single quote is literal.

    Inputs that now parse differently, shown as the words each gives (arguments after !say ):

    • don't and it's Ada's: Now don't, then it's and Ada's. Before, the apostrophe opened a span, so don't was rejected as an unclosed single quote and it's Ada's gave the one word its Adas
    • a'b c' and key='a b': Now a'b with c', and key='a with b'. Before, ab c and key=a b
    • a''b, a'b'c and a': Now unchanged as a''b, a'b'c and a'. Before, ab, abc and an unclosed single quote
    • 'it's fine', 'don't' and 'x'y': Now it's fine, don't and x'y. Before, all three were rejected as an unclosed single quote
    • 'the dogs' bowl': Now the dogs and bowl'. Before, rejected as an unclosed single quote
    • 'a'b, 'a b'c, ''a, 'a'1 and '日本'語: 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, a1 and 日本語
    • a' 'b and x'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 b and xy z

    Inputs that parse as before include 'Yes', 'No' (Yes, and No), 'a'., 'a'-b, 'a' 'b', 'a''b' (ab), don\'t and every input without a single quote.

    Rejections that commands.parseQuoted returns for an unclosed quote and a trailing backslash now also carry name, the command name read before the mistake, so a reply can say which command it was for. A parse and a rejection can therefore both carry name, and a caller that told them apart with "name" in result must use "reason" in result instead.

    The reason of a commands.parseQuoted rejection 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. The reason for a single quote without that cause, for a double quote and for a trailing backslash is unchanged, and the rejection keeps its name. Code that compared reason with the exact text of the first form must match the new text, or the shared start A 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 as key="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 Default or Native prefixes, and the per-resource request option types become RequestOptions and AuditedRequestOptions

    Each entry point exports a type that differs between the APIs under one name with its own shape, as Client already did. In @neontechspace/fluxerly, RequestOptions has signal and timeoutMs, and in @neontechspace/fluxerly/effect it has timeoutMs. AuditedRequestOptions adds auditReason in both. Notes that the removed option types carried, such as the per-page deadline of messages.iterateSearch, now sit on the methods that use them. The reply helpers of default command and event contexts take Omit<RequestOptions, "signal">, because they apply the handler's signal. Runtime behavior is unchanged

    Migration:

    Both

    ChannelOperationOptions, GuildOperationOptions, MessageOperationOptions, SendOptions, UserOperationOptions, CountOperationOptions, BotApplicationOperationOptions, MessageSearchOptions, AttachmentRefreshOptions, OAuthOperationOptions, InstanceResolveOptions

    RequestOptions

    Both

    ChannelAuditOperationOptions, GuildAuditOperationOptions, MessageAuditOperationOptions, ModerationOptions, WebhookOperationOptions

    AuditedRequestOptions

    Default

    Default plus any name in the two rows above

    RequestOptions or AuditedRequestOptions

    Default

    Default<Name>, such as DefaultCanManageOptions, DefaultCollectorOptions, DefaultRestRequest, DefaultPrefixCommandContext, DefaultSupervisor, DefaultGuards or DefaultCommands

    <Name>

    Effect

    Native<Name>, such as NativePrefixCommandContext, NativeSupervisor, NativeGuards, NativeCommands or NativeCommandRequirements

    <Name>

    In the default entry point, names such as CanManageOptions, CollectorOptions, PrefixCommandsOptions, SupervisorChildOptions and RestRequest now name the default shape, which includes signal where the old Default type did. The Effect entry point no longer exports the default-API PrefixCommandsOptions and SupervisorChildOptions shapes, and those names now refer to its own

  • 7733b23: Make request errors say what to change. Rejections carry a hint for 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 an invalid_client error or a 401 from a code exchange, refresh, revocation or introspection, and the user's access token for a 401 from fetchIdentity, fetchGuilds or fetchConnections. Operation error messages lead with the failed operation and Fluxer's answer, for example Message send failed: Fluxer reports that the bot lacks a required permission (MISSING_PERMISSIONS, HTTP 403), and explanations name Fluxer instead of "the provider"

    Keep Fluxer error codes that this SDK version does not describe yet. An uppercase code of up to 64 characters is recorded as details.providerCode and named in the message while apiError stays null, and an unrecognized validation code stays in apiError.validationErrors with its field path and a generic explanation. OAuthOperationError and MessageCleanupError keep unrecognized codes the same way. A response that does not match the expected shape names the failing field or check, such as type or channelMismatch, in details.responseField and logs a rest.responseRejected Warn. A message cleanup that fails in a history read or batch request copies that step's details.responseField. A client.rest.request route has no expected shape, so its unusable response is only returned in the Result. The members.fetchCanManage and permissions.fetch checks keep these facts and the failed step's error as cause when they report a failure under their own operation name. OperationErrorOptions and the MessageError and MessageCleanupError constructors accept providerCode and responseField, and the OAuthOperationError constructor accepts providerCode

    Log HTTP 401 and 403 rejections as a rest.rejected Warn with the Fluxer code and hint, even when the application handles the Result. When an event handler or command fails with the rejection and no onError hook 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 same logging option 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, and rest.request Debug records carry the Fluxer code in fields.apiError

    A ConfigurationError thrown by the default createClient or runBot points at the caller's line

    Migration: ApiValidationErrorDetail.providerCode is now ApiValidationCode | (string & {}), so code that assigns it to an ApiValidationCode must narrow it first. Operation error message text changed, so match on code, reason and apiError rather than on messages

  • abb5d3f: The coding agent guide moved from consumer/AGENTS.md to agents/AGENTS.md in the installed package. An application's AGENTS.md section from an earlier fluxerly agents run points at the old path, so run npx --no fluxerly agents, pnpm exec fluxerly agents or bunx --no-install fluxerly agents again after updating

    The README now links the documentation's llms.txt for agents that read web pages, and its Effect section describes the accepted Effect 4 range instead of an exact version

  • abb5d3f: Add ChannelType.Announcement (5), GuildAnnouncementChannel, GuildTextChannelBase and AnnouncementChannelCreate to both APIs. Announcement channels now decode into their own shape instead of GuildUnknownChannel, so exhaustive channel-type switches need an announcement case

    Allow ChannelEdit.type to 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 a guildChannelUpdate event replaces the cached channel with its new type

  • 66e4f2b: Audit-log reads and guildAuditLogEntryCreate events no longer fail on an audit action this SDK version does not name. AuditLogEntry.actionType is now a number that keeps any action Fluxer records, including ones added later, so compare it with AuditLogActions values. Previously one such entry rejected its whole audit-log page and made the gateway event malformed. AuditLogActions gains ThreadCreate (110), ThreadUpdate (111) and ThreadDelete (112), which also work as actionType filters

  • 7733b23: Ban durations are milliseconds, like member timeouts, command cooldowns and the duration command argument. The BanInput fields are now durationMs, 0 for a permanent ban or 60,000 through 63,072,000,000 for a temporary one, and deleteMessagesMs, 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 as input.durationMs, and states its whole-second range

    Audit-log entries report the message deletion of a member ban in milliseconds too, so the AuditLogOptions field deleteMessageSeconds is now deleteMessagesMs, in both API styles and in guildAuditLogEntryCreate events

    A duration command argument accepts wholeSeconds: true, which rejects a value with a millisecond part such as 1m500ms and adds "in whole seconds" to the rejection reply. With wholeSeconds: true, min: 60_000 and max: 63_072_000_000, the argument's value can be passed to durationMs unchanged, and a moderator's out-of-range value gets the usage reply instead of a failed ban

    Migration:

    • Replace durationSeconds: n with durationMs: n * 1000 and deleteMessageSeconds: n with deleteMessagesMs: n * 1000 in ban input
    • Replace options.deleteMessageSeconds with options.deleteMessagesMs on audit-log entries, and divide by 1000 where seconds are still needed
  • 7733b23: The command argument types that pick from a fixed list are renamed to userChoice, channelChoice and roleChoice, next to choice, so the names user, channel and role no longer suggest a lookup in the message's community. They still select one of up to 100 candidates given at registration. The old names now fail registration with ConfigurationError, 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 was user, and the new list form

    Migration: Replace type: "user", "channel" and "role" with "userChoice", "channelChoice" and "roleChoice" in argument schemas, and in code that reads CommandArgumentMetadata.type

  • 07d2b9d: A prefix command parser can now explain why it declined a message. The commands.parseQuoted parser now returns a new PrefixCommandParseRejection with a fixed reason for a quote that never closes or a backslash at the end, where it used to return undefined and give no hint, and a custom parse function can return the same { reason } shape. The router's onUnmatched callback, which already handles unknown names and groups typed without a subcommand, receives the reason on CommandParserRejected in both entry points, and the Debug commands.unmatched record 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 carry name in command-name syntax. A rejection with an empty or non-text reason, an invalid name or 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 of commands.parseQuoted that tests the result for truthiness or reads args must now rule out a rejection first, such as with "reason" in result, because only a rejection has reason and a parse has name, args and rawArgs

  • 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 reason input before any request. Subscriptions, collectors and state observers all end with close(), and a collector's result() returns its final collection. MessageError reports outcome with the same notDispatched, rejected and unknown values as other operation errors, so an API rejection is now distinguishable from a send that never started

    Migration:

  • 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 referencedMessage type is now ReferencedMessage, not an address-only MessageReference. Hand-authored message fixtures must supply the resolved message shape or omit the optional field. Address-only operations can continue to use MessageReference. Additional retained context counts toward configured cache and collector byte budgets

    Add 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.field method takes its display setting as a named option, following the rule that settings are option objects rather than boolean positionals. The new EmbedFieldOptions type describes it

    Migration: Replace field(name, value, true) with field(name, value, { inline: true }), and drop a false third argument. A leftover boolean or other non-object third argument throws ConfigurationError with a hint naming { inline: true }

  • c8c87b0: Embed.provider and EmbedChild.provider now have their own EmbedProvider type, with only name and url, matching what Fluxer sends. They were typed as EmbedAuthor, whose iconUrl and proxyIconUrl never arrived on a provider. EmbedProvider is exported from both entry points, and a provider's icon fields are no longer decoded

  • 7733b23: Base every SDK error on the exported FluxerlyError with a stable code, an optional fix hint, frozen details, a standard cause and toJSON(). 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 as ECONNRESET, never the transport message. API validation failures include the sanitized field path with each error code. Messages and toJSON() output mask credential patterns, including application text quoted by ApplicationError and SdkDefect, while cause keeps the original value. Masking applies to error text, describeError output and log records. It covers bare Fluxer bot tokens of the form <application_id>.<secret>, keeping the application ID. It also covers the value after an assignment to a key ending in token or secret, such as FLUXER_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 as Missing token: check the configuration stays readable

    Add 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 than ConfigurationError and SdkDefect. It also accepts an Effect Cause and describes each failure, defect and interruption, so native programs can pass exit.cause directly. A thrown value that is not an Error is named Non-Error value (string) and similar. Add errors with apiCode, isRetryable and match. The errors.apiCode(error) helper returns the recognized Fluxer rejection category of an operation error, such as "missingPermissions" or "twoFactorRequired", and undefined for every other value, so errors.apiCode(result.error) === "missingPermissions" works on any error of an operation's Result without checking its _tag first. A read that failed after it was sent records details.read, so errors.isRetryable treats it as safe to repeat, unlike a write with an unknown outcome. Add ApplicationError for failures raised by application callbacks such as a runBot setup or commands callback or a keepTyping task

    Migration: MessageOperationError, ChannelOperationError, GuildOperationError, UserOperationError, WebhookOperationError, BotApplicationOperationError, AttachmentRefreshError, MessageCleanupError, MessageError and OAuthOperationError now take one options object, described by OperationErrorOptions for the resource operation errors. Replace new AttachmentDownloadError(reason, status, inputValidation) with new AttachmentDownloadError({ reason, status, inputValidation, cause }). CountOperationError, MemberChunkError, PaginationError and PresenceError change the same way, taking { operation, reason, inputValidation, cause }, { reason, retryAfterMs, inputValidation, cause }, { operation, reason, inputValidation, cause } and { reason, inputValidation, cause }. PresenceError no longer defaults its reason to input, and omitted status, retry and input detail values still default to null. ConfigurationError, ConnectionError, HelperError, AssetUrlError, CollectorError, EventWaitError, SupervisorError and SupervisorChildError accept a trailing options object with cause. ConfigurationError also accepts hint there, and ConnectionError accepts hint and details

  • 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 new onError option, or is logged at Error with its full message, stack and cause chain. A FailureReport carries the original thrown value unchanged, its kind, and the event, command, subscription and message IDs involved, and describe() returns readable text. Native reports also keep the Effect cause

    A 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 onUnmatched callbacks and parsers, and rejected or unmatched commands are logged at Debug and counted

    Migration: HandlerErrorReport is replaced by FailureReport, and the message cache onError option and CachePolicyErrorReport are removed in favour of the client's onError. A message cleanup onProgress callback that throws, rejects or returns an Err is now reported to onError with kind progress instead of being ignored, and cleanup continues. The callback stays synchronous, so a returned Effect is not run and is reported as a TypeError. SdkDefect now keeps each fault's original value in reasons and its first fault as cause. 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's aborted getter or listener methods, has origin application and code application.defect. Other faults, including faults in SDK work around those reads, have code sdk.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.reason set to protocol and reconnects with a new session. Three such failures in a row without a healthy connection between them end the shard with a protocol ConnectionError as before. Oversized frames, invalid UTF-8, binary frames and gateway: { onMalformedDispatch: "terminate" } still end the shard without retrying

    Migration: A single invalid gateway message no longer ends client.run, because the shard reconnects. The protocol ConnectionError now arrives only after three such failures in a row

  • 7733b23: The runBot function lets running work finish before it stops, in both entry points. When its signal aborts, or SIGINT or SIGTERM arrives with processSignals: 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 new drainMs option 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 behavior

    Add ShutdownOptions with drainMs to client.shutdown(options) in both entry points for the same drain outside 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, logging lifecycle.drained. When the time runs out, a lifecycle.drainTimedOut Warn record names how many handlers, events and requests were cut off. A lifecycle.draining Info record marks the start. An invalid drainMs throws ConfigurationError in the default API and dies with it in the Effect API

    Supervised children drain the same way when the supervisor stops them, for up to the supervisor's shutdownTimeoutMs minus one second, which keeps that second for closing the connection. A child whose client stopped on its own is not drained

    Migration: To keep cancelling running handlers as soon as the bot is asked to stop, pass drainMs: 0 to runBot

  • 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 guildCreate event 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 others

    Migration: Code that relied on community caches being empty after a reconnect should call guilds.fetch and 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 gateway.dispatchRejected with the event type and failing field path, clears cache entries the event could have changed and counts it. Set gateway: { onMalformedDispatch: "terminate" } to end the shard with a protocol ConnectionError instead

    Add an overflow handler option with stop, dropOldest and dropNewest policies. Every dropped event is logged at Warn and counted in diagnostics().counters.eventsDropped. Handlers registered with client.on drop the oldest waiting event by default when their queue is full, as runBot handlers and command routers do, and the handler keeps running. An unknown event name fails with ConfigurationError whose hint suggests the closest valid name when one is similar

    Migration: A full client.on queue no longer ends the subscription. To keep that behavior, where waitForClose returns EventOverflowError, pass { overflow: "stop" } to client.on. The subscribe streams, event waits and collectors still end on overflow. Event handlers passed to runBot now run messageCreate up 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, maxPendingMessages, maxPendingBytes, onError } instead 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 message

  • abb5d3f: Received message.messageReference now has its own type, MessageContextReference, separate from the MessageReference that message operations take in both APIs. Its channelId is required and id is 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 fails

    This is a breaking type change: Check that the received context has an ID and build a MessageReference from both fields before passing it to fetch, reply or another message operation

    Add MessageType.ChannelFollowAdd and the received-only flags MessageFlags.Crossposted, MessageFlags.IsCrosspost and MessageFlags.SourceMessageDeleted. Fluxer sets these publishing flags itself. Sending and editing continue to accept only SuppressEmbeds and SuppressNotifications, with other flags rejected before sending the request

  • 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:

    Migration: Replace createPkce() with oauth.createPkce(), and each loose hierarchy function with its hierarchy method

  • 7733b23: Reject misspelled and unsupported top-level option keys in createClient, runBot and supervisor.create in both entry points with ConfigurationError, whose hint names the closest supported key, such as events for event. The clientOptions of supervisor.child.run are checked the same way, where an unknown key was silently dropped before, so the child client ran without it. Unsupported keys in the nested logging, cache, cache.messages, cache.<kind>, connection, connection.recovery, uploads, gateway, sharding, instance and supervisor restart settings are named in the message with the same suggestion, and a removed logging setting names its replacement

    Normalize the configured token: Surrounding whitespace and one pair of matching quotes, which .env files often add, are removed. A token that starts with a Bot or Bearer scheme is rejected with ConfigurationError, since the SDK adds the Bot scheme itself. The auth.rejected hint suggests checking the configured value before regenerating the token

    Migration: Remove option keys that createClient, runBot, supervisor.create or the clientOptions of supervisor.child.run do not support, including unknown nested keys such as a connection key other than startupTimeoutMs, maxStartupAttempts and recovery, and pass the bare token without a Bot or Bearer prefix

  • abb5d3f: Remove the retired phone-verification level. The GuildVerificationLevels.VeryHigh constant is gone and guild edits reject level 4 before the request. Level 4 received from older instances decodes as High (3), matching Fluxer. Other out-of-range levels still fail decoding. The ApiProviderCode type drops GUILD_PHONE_VERIFICATION_REQUIRED, and an unrecognized code stays in 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 in hierarchy and the assets and links helpers returned by client.instance.resolve(). Invalid input throws the same HelperError, AssetUrlError or GuildOperationError with the same operation and reason that the Result or Effect previously carried. In the native API such a throw inside Effect code becomes a defect rather than a typed failure

    Add format.tryParseMention, format.tryParseTimestamp, format.tryParseCustomEmoji, snowflakes.tryParse and colors.tryParse for 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 with HelperError in the native API

    Migration:

  • 7733b23: Make public types describe what Fluxer actually sends and what builders accept. GuildChannel is a union of GuildTextChannel, GuildAnnouncementChannel, GuildVoiceChannel, GuildCategoryChannel, GuildAnnouncementThreadChannel, GuildPublicThreadChannel, GuildPrivateThreadChannel, GuildForumChannel, GuildMediaChannel, GuildLinkChannel and GuildUnknownChannel. Comparing channel.type with a ChannelType constant narrows it to exactly that shape, so if (channel.type === ChannelType.Voice) yields a GuildVoiceChannel. A channel type this SDK version does not know arrives as a GuildUnknownChannel whose type is "unknown" and whose rawType holds the number Fluxer sent, so a switch on type that also handles "unknown" is exhaustive. PresenceUpdate.status is an ObservedPresenceStatus, and an unrecognized status from Fluxer becomes "unknown" instead of an arbitrary string. EmbedBuilder.color accepts any ColorInput, such as "#ff8800" or an RGB tuple, and EmbedBuilder.timestamp accepts a Date, epoch milliseconds or an ISO string. An invalid color, or an invalid Date or number, throws HelperError with operation embed.color or embed.timestamp. The type error from calling build() on an empty MessageBuilder now names the missing body, through the exported MissingMessageBody type. Default clients, OAuth clients, webhook clients, subscriptions and collectors support await using, which shuts a client down or closes a subscription or collector and waits for its cleanup

    Migration:

    • Narrow a GuildChannel on type, for example with ChannelType.Voice, before reading bitrate, userLimit or other fields that belong to one channel type
    • Handle channel types outside ChannelType by checking channel.type === "unknown" and reading the Fluxer number from channel.rawType. A numeric type no longer carries an unknown type
    • Compare PresenceUpdate.status against the listed values and handle "unknown" instead of Fluxer strings outside that list
    • EmbedBuilder.color(value) converts its input, so pass the color directly rather than converting it first. A number outside the 24-bit range now throws
    • EmbedBuilder.timestamp(value) converts a Date or number with toISOString(). An invalid Date or number now throws instead of producing an invalid embed
  • 7733b23: Public types now use one name per concept, and API rejection details are readable in the reference, in both entry points

    • MemberSearchHit.globalName and OAuthIdentity.globalName are now displayName, the name User and MessageUser already use for the account-wide display name. The display.name helper therefore reads it from member-search hits and OAuth identities too
    • The deprecated MessageMention alias is removed. MessageUser is the only name for the partial-user shape in mentions and references
    • ApiErrorDetail and ApiValidationErrorDetail are plain interfaces. The new ApiErrorCode, ApiProviderCode and ApiValidationCode unions list every recognized category and server code to branch on. The explanation fields are typed string, and validationErrors is present only when code is invalidFormBody
    • The @neontechspace/fluxerly/effect entry point no longer exports the ten default-API option types DefaultMemberChunkOptions, DefaultCountOperationOptions, DefaultBotApplicationOperationOptions, DefaultExpressionDeleteOptions, DefaultUserOperationOptions, DefaultWebhookOperationOptions, DefaultAttachmentDownloadOptions, DefaultAttachmentRefreshOptions, DefaultAttachmentStreamOptions and DefaultMessageCleanupOptions. No Effect member accepts them

    Migration:

  • 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 ConfigurationError in 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, onError hooks, state observers and supervisor configuration, is now reported like a throw of its error

    The 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 the Result and ResultAsync types. Callback return types accept unknown, and handler, collector and supervisor signals are typed as AbortSignal. A missing or blank token is accepted by the types as string | undefined and throws ConfigurationError with a hint that the environment variable holding it is probably unset

    Migration:

  • 7733b23: Command id arguments 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 to 9223372036854775807. The same limit applies to IDs inside the accepted mention forms. The value 0 and longer or larger values are now rejected as Invalid before the command runs. Commands that relied on 0 or out-of-range tokens reaching the handler need a custom argument 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 commands.memoryCooldowns() and to each router's own store
    • The default limit rises from 1,024 to 10,000 keys, and the router option cooldowns: { maxEntries } sizes a router's own store, for example commands.create({ prefix: "!", cooldowns: { maxEntries: 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 in help throws ConfigurationError as for a missing one. Hidden commands still run when invoked, so guard the ones that need it

    Migration:

    • The CooldownCapacity claim and the CommandCooldownCapacity rejection are removed, because a built-in store no longer refuses a key. A custom store returns only CooldownAcquired or CooldownActive, and any other claim fails the command with ConfigurationError. 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 maxEntries to commands.memoryCooldowns or the router's cooldowns option
    • Owner-only and other private commands that must not appear in help set hidden: true instead of filtering them in every help call's include
  • 7733b23: Registering a subscription or collector after client shutdown has begun no longer fails with ClientClosedError at registration, in both entry points. A handler that is still running when shutdown starts can register, which is a runtime race rather than misuse

    Migration:

  • 7733b23: Configure a whole bot with one runBot options object in both entry points. A commands option routes prefix commands, setup runs after every handler is registered and before the connection starts, and onError and processSignals sit beside them. The runBot function checks every option, including event names, event delivery settings and client settings, before any client, process signal listener or request exists, even when the signal is already aborted. Invalid configuration other than a missing or blank token throws ConfigurationError at the call, or is a defect in the native API. It then creates the client and registers every event and command synchronously. An aborted signal with valid options returns success without creating a client. A failed setup or an application error thrown by a commands register callback is an application failure, reported as ApplicationError with the original value as cause. In the default API, a setup callback that throws, rejects or returns an Err gives source "runBot setup", and in the native API a failed setup Effect gives the same. A register callback that throws gives source "runBot commands" in both APIs. A failed setup stops 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 is ConfigurationError, and an SdkDefect or a native defect keeps its original cause

    Commands gain features for common bot needs:

    • The guard option accepts one guard or an array, and a guard can return { deny: "reason" }. The exported guards provide guildOnly(), dmOnly(), ownerOnly(ids) and requirePermissions(names)
    • The guildOnly() guard allows a message sent in a community, which Fluxer calls a guild, and denies every other message without a request. The dmOnly() 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 with directMessages.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 is UserOperationFailure
    • The requirePermissions(names) guard 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 list
    • The cooldown option accepts { durationMs, per } with per set to "user", "channel" or "guild", and uses a memory store owned by the router when store is omitted
    • The router's use option adds middleware that wraps every command and can stop it by not calling next. In both APIs, next fails with the command's own error, and each middleware stage and the command run at most once per message, even when next is 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 as Missing name. Usage: !greet <name>, the guard's reason or the cooldown's remaining time. The commands of runBot use it by default, while a router from commands.create gives no feedback unless onReject is set. Setting onReject: "silent", on a router or on one command, sends nothing, and the rejection is still counted and logged at Debug as commands.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 with feedbackSuppressed. An onReject function receives every rejection. Native replies measure the remaining time with the handler's Effect Clock
    • Setting mentionPrefix: true accepts a mention of the bot, <@id> or <@!id>, as a prefix, and the prefix option can be resolved asynchronously. The bot's ID comes from the user in the gateway's READY. When READY lacks it, one shared users.fetchSelf read 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 onUnmatched and its Debug commands.unmatched log record carry suggestion, the closest registered command or group name when one is similar. Two swapped adjacent characters count as one edit, so pnig suggests ping. Hidden commands and everything inside a hidden group are never suggested
    • The bound reply accepts a plain string
    • Argument descriptors add min, max and default for integers and numbers, and new duration, member and custom types. A member argument accepts only IDs in the 64-bit ID range, as id does. A custom parse 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 with ConfigurationError for field command, without an unhandled rejection

    Migration:

    • The runBot(options, install, runOptions) form is removed. Move event handlers into events, commands into commands, other startup work into setup(client), and runOptions settings such as signal and processSignals into the options object
    • Code that checked runBot's Result for configuration failures catches the thrown ConfigurationError instead, except for a missing or blank token, which stays in the Result. The Result reports connection failures, cancellation and stopped workers as before, and a failed setup or commands callback as ApplicationError. A handler or command router set to overflow: "stop" that overflows is reported and stops the bot, and the run fails with CriticalWorkerStoppedError, whose cause is the subscription's EventOverflowError. So EventOverflowError is no longer part of the runBot error type in either API. Match CriticalWorkerStoppedError instead in code that matches on runBot failures, or set overflow to dropOldest or dropNewest to keep the bot running
    • CriticalWorkerStoppedError.workerIndex counts event subscriptions in configuration order, followed by the command router
    • A cooldown without store no longer requires one. Pass per instead of building the key from the message when the default key fits
    • Keyed registerMany entries may omit arguments for unrestricted raw args. TypeScript projects with exactOptionalPropertyTypes omit it rather than pass arguments: undefined
    • The commands of runBot now answer a rejected command. Set onReject: "silent" in the commands option of runBot to keep rejected commands unanswered. Unmatched commands, and rejected commands of a router from commands.create, still get no reply unless onUnmatched or onReject asks for one
  • dd52acd: The runBot function now skips messageCreate and messageUpdate events written by bots, including the bot's own messages, before they reach its events handlers or its commands router, in both entry points. A reply can no longer trigger the handler that sent it, and handlers no longer need a message.author.isBot check. The new ignoreBots option controls this in one place and defaults to true. Setting it to false lets both the handlers and the prefix commands see bot-authored messages. A commands.ignoreBots setting, when present, still decides for the prefix commands alone. A value other than a boolean is misuse. The createTestBot functions of both testing entry points accept the same option

    Migration: A bridge, logging or moderation bot that handles messages from other bots passes ignoreBots: false to runBot. Bots built with createClient and client.on are not affected

  • 944c7bf: A missing or empty token no longer makes runBot throw in the default API or die in the Effect API. Once the other options are valid, runBot reports it like any failed run before creating a client: It logs the ConfigurationError once without a stack trace, sets process.exitCode to 1 unless reportFailure is false, and returns it as an Err in the default API or fails with it in the Effect API. Both runBot failure types now include ConfigurationError. Other invalid options still throw in the default API and die in the Effect API, and createClient still rejects a missing token as misuse

    The missing-token hint now also covers projects created by fluxerly init: Set FLUXER_BOT_TOKEN in the .env file that init wrote

    Migration: Code that caught the thrown ConfigurationError for a missing token around a default runBot call reads it from the returned Err instead. Effect code that handled the defect handles the typed ConfigurationError failure

  • 37612fd: A runBot handler set to overflow: "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 with CriticalWorkerStoppedError, whose details hold the event, the exceeded limit, messages or bytes, and its capacity, and whose cause is the subscription's EventOverflowError. The overflow itself is still reported to onError or logged. A bot that should keep running sets overflow to dropOldest or dropNewest, which are unchanged

  • dd52acd: The default API's runBot now handles SIGINT and SIGTERM unless processSignals is false. Ctrl+C or a stop from a process manager such as Docker, systemd or PM2 requests a normal stop that drains running work for drainMs, then the listeners are removed and the process can exit. The runner still never exits the process itself, and processSignals: true is no longer needed

    The Effect API's runBot keeps process signals opt-in. A launcher such as NodeRuntime.runMain already interrupts the program on these signals, and a second handler would race its interruption. Set processSignals: true there only when nothing else handles the signals

    Migration: An application that handles SIGINT or SIGTERM itself, or runs several bots in one process with its own shutdown, passes processSignals: false to the default runBot

  • 7733b23: The runBot function 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 code lifecycle.botFailed and sets process.exitCode to 1. It skips the record when the client already logged the same error, such as the lifecycle.connectionEnded record 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 ConfigurationError and a defect rejects with SdkDefect, which Node.js prints before it exits with a failing code. A bot entry point therefore needs no try/catch: await runBot({ ... }) is complete in the default API, and so is await Effect.runPromiseExit(runBot({ ... })) in the native API

    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: null for such channels, so MessageSearchChannel.name is now string | null when 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 childOutput supervisor option with prefix, inherit and ignore. Readable output labels each line with its shard or child ID. JSON output stays valid JSON Lines: A child's SDK record gains fields.child, another JSON object passes through unchanged and other text becomes a supervisor.childOutput record. With prefix, children receive the supervisor's format and color through FLUXERLY_LOG_FORMAT and FLUXERLY_LOG_COLOR unless 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 new logging supervisor option accepts the client logging settings for these records

    Migration: Supervised child output was previously discarded. Set childOutput: "ignore" to keep discarding it

  • 7733b23: Supervisors now restart a crashed child by default, with the documented defaults of SupervisorRestartOptions: 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 omitted restart option meant that one crashed child stopped the whole supervisor and took every shard offline. The restart option now also accepts false, and the supervisor.crash record names that setting when it stops the supervisor

    Restart budgets now count consecutive crashes instead of every crash over a child's lifetime. A child that runs for the new restart.healthyResetMs (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 supervisor.restart record has fields.budgetReset set to true. A crash loop still exhausts maxAttempts and stops the supervisor

    Migration:

    • 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 restart.healthyResetMs to a period longer than the expected process lifetime, up to 2,147,483,647 ms. The restarts count in status() child snapshots also starts again after such a reset
  • 314ac29: Test clients in both testing entry points now answer message sends (POST /channels/:id/messages) and edits (PATCH /channels/:id/messages/:id) that no rest.respond registration 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 a testing.unmatchedRequest Warn

    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.next and idle, no longer fail after a default 2,000 ms. Without timeoutMs they 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 explicit timeoutMs still fails the wait with TestTimeoutError

    Migration: A test that expects TestTimeoutError from a wait without options passes timeoutMs

  • dd54820: Threads, forum channels and media channels now have their own channel shapes in both entry points, instead of arriving as GuildUnknownChannel. ChannelType gains AnnouncementThread (10), PublicThread (11), PrivateThread (12), Forum (15) and Media (16), and GuildChannel gains GuildAnnouncementThreadChannel, GuildPublicThreadChannel, GuildPrivateThreadChannel, GuildForumChannel and GuildMediaChannel. A thread flattens Fluxer's thread metadata, such as archived, locked and autoArchiveMinutes, and carries the bot's own membership when Fluxer reports it. The isThreadChannel function 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 as guildChannelDelete. Text and announcement channels gain defaultAutoArchiveMinutes and defaultThreadRateLimitPerUser. The new constants are ChannelFlags, ThreadAutoArchiveMinutes, ForumSortOrder and ForumLayout. The channels.get and channels.fetch methods can return any of these shapes, and channels.fetchAll never lists threads

    Permissions gains ManageThreads, CreatePublicThreads, CreatePrivateThreads and SendMessagesInThreads, so permissionBits and guards.requirePermissions accept their names. MessageType gains ThreadCreated (18) and ThreadStarterMessage (21). MessageFlags gains the server-managed bits HasThread (32) and FailedToMentionSomeRolesInThread (256), which message and webhook send and edit operations reject before dispatch. Message.thread holds the thread a message started, and GuildListSummary.threadsActive reports whether the bot can use threads in a community. The testing fixtures gain thread() and forumChannel() with the WireThread and WireForumChannel types, and guildCreate() now includes an empty threads list

    The new client.threads namespace creates, changes, lists and searches threads and manages their members. Its create method starts a thread in a text or announcement channel, createFromMessage starts one on an existing message, and createPost creates a forum or media post with its first message, sending its files in the same multipart request. The edit method archives, locks, renames or pins a thread and replaces a post's tags. For reading, fetchActive lists a community's active threads, fetchArchived reads one page of a channel's archived threads, and search reads one page of matching threads, with the first message of each post in a forum or media channel. Every list carries the bot's own membership on 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 retry

    Thread membership changes through join, leave, addMember and removeMember, and thread members are read with fetchMember, fetchMembers and iterateMembers. With withMember, 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 are ChannelOperationError values whose operation names the threads method, and a missing-permission rejection of create, createFromMessage, createPost, edit, fetchArchived, search, addMember or removeMember lists the permissions that method needs

    Forum and media channels can be created and managed through channels. Creating a channel accepts ChannelType.Forum and ChannelType.Media with availableTags, defaultReactionEmoji, defaultSortOrder, defaultTagSetting, flags, a topic of up to 4,096 characters and, on forum channels, defaultForumLayout. Text and announcement channels accept defaultAutoArchiveMinutes and defaultThreadRateLimitPerUser on creation, and channels.edit accepts those two settings and every forum setting, including a complete availableTags list. On creation, a setting that the chosen channel type cannot have fails with reason input before any request. An edit cannot tell the channel's type, so channels.edit accepts every setting and leaves that check to Fluxer. Both methods fail with reason input before 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 new channels.createForumTag, channels.editForumTag and channels.deleteForumTag change one tag and return the updated forum or media channel. The editForumTag method replaces the tag, so an omitted moderated value or emoji resets. A permission rejection of any of the three lists ViewChannel and ManageChannels in details.requiredPermissions

    Webhook clients can now reach threads. WebhookMessageInput gains threadId, sent as the thread_id query parameter, and, on new messages, threadName to start a forum or media post with up to five appliedTagIds. The SDK rejects threadName together with threadId, appliedTagIds without threadName, and threadName on a reply or forward before any request. The fetchMessage, editMessage and deleteMessage methods take WebhookMessageOperationOptions with a threadId to reach a message in a thread

    Thread gateway events now have their own typed events. The threadCreate event delivers a ThreadCreateEvent, the thread plus isNewlyCreated, which is false when the bot joined or was added to an existing thread. The threadUpdate event delivers the changed thread, threadDelete a ThreadDeletion with the thread's ID, community, parent and type, threadListSync a ThreadListSync with the bot's active threads in a community or in some of its channels, and threadMembersUpdate a ThreadMembersUpdate with 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 keeps THREAD_DELETE while the message cache is enabled

    The 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 gateway.threadSkipped Warn 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 threads.fetchActive list 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. A channels.fetchAll result keeps cached threads, and an operation confined to one thread clears only that thread

    Deleting 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 SendMessages in the thread exactly when SendMessagesInThreads is granted. The new PermissionInput.parentChannel supplies that parent to permissions.calculate, which applies the same rule. A thread without its parent, a thread with a different channel as parentChannel, and a parentChannel beside a channel that is not a thread fail with reason input. For a thread, permissions.fetch also reads the parent channel under the same deadline. The guards.requirePermissions guard 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

    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.search reject the whole page and stopped messages.iterateSearch. MessageSearchResultsPage.threads now holds those threads as typed thread shapes, each with the bot's membership when the bot has joined it, and channels keeps the other channels. Each message's channel appears exactly once across channels and threads. Audit-log pages and guildAuditLogEntryCreate events also no longer fail on a forum or media channel change, because its available_tags and default_reaction_emoji values now decode as ForumTag lists and ForumDefaultReaction objects, which AuditLogChangeValue gains, and AuditLogPage.threads lists the threads that the page's thread actions target

    Rejections of thread, forum tag and webhook post operations carry a reviewed apiError. The new ApiErrorCode categories are threadArchived, threadLocked, threadAlreadyCreated, channelHasThreads, invalidChannelType, forumTagNamesNotUnique, forumTagRequired, noTagsAvailable, mediaChannelRequired, webhookForumTargetRequired and webhookThreadNameRequiresForum, each with a hint that names the fix. The active thread, forum tag, pinned post and thread member limits join resourceLimit, and a missing forum tag or thread member joins unknownResource. ApiProviderCode gains the matching Fluxer codes

    Migration: Code that matched type === "unknown" for a thread, forum channel or media channel now receives the typed shape, and an exhaustive switch over GuildChannel, ChannelType, ChannelCreate or ApiErrorCode needs the new cases. A permissions.calculate call for a thread passes the parent channel as parentChannel. A gateway.ignoredEvents list that names a thread dispatch an enabled cache needs, which is any thread dispatch for the channel cache and THREAD_DELETE for the message cache, now fails client creation with ConfigurationError, 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 a Z or ±hh:mm offset, 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. GuildEdit.messageHistoryCutoff now 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 digits

    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.parse had 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 input and an inputValidation constraint of allowedFields. Its explanation names the key, suggests the closest supported key and lists the supported keys, for example Unsupported option "timeout" in the operation options. Did you mean "timeoutMs"? Supported options are timeoutMs, 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, exchangeCode and revoke inputs, and the default API gateway.send options. Channel position entries and permission overwrites report an unsupported key separately instead of as a general format failure. The instance.resolve and cache.entries options name an unsupported key and suggest the closest one in their ConfigurationError

    Migration: 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 logging with level, per-category categories, debug, format, sink, dedupe and unsafe settings, or set FLUXERLY_DEBUG=1 or 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. The FLUXERLY_LOG_FORMAT and FLUXERLY_LOG_COLOR environment variables choose the format and color for a client without format. The color variable accepts 1, true, yes or on to force color and 0, false, no or off to disable it, and an unrecognized value of either variable logs one sdk.unknownEnvironmentValue Warn. Without sink or format, the native Effect API logs through the caller's Effect logger with fluxerly.* annotations, including fluxerly.error.code, fluxerly.error.hint and fluxerly.error.* details for a failure. Each error in the cause chain adds its own annotations under fluxerly.error.cause., then fluxerly.error.cause.cause. and 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 diagnostics().counters 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 ratelimit.deadline and is not counted as a wait. Failure reports logged instead of reaching an onError hook carry fields.reportOutcome (queueFull, interrupted, clientClosed or subscriptionClosed), and collapsed records carry the suppressed count in fields.repeated. The lifecycle.retry and lifecycle.connectionLost records carry fields.next, which is resume or identify and names the next handshake

    Unsafe payload logging prints its Warn banner once before the first payload record at any level, prints nothing for a silent category and reads at most the first 64 KiB of each received REST body

    The 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 metrics

    Migration: The development, measurements, minimumLevel and logger logging settings, fromStructuredLogger and fromEffectLogger are removed. Use level and categories for levels, debug or FLUXERLY_DEBUG for Debug records, and sink to forward records to another logger. The types DefaultLogger, DefaultLoggingOptions, StructuredLogger, SdkLifecycleEvent, SdkLifecycleLogRecord, SdkOperationalLogRecord, SdkMeasurementLogRecord, SdkMeasurementOperation and SdkMeasurementStage are removed. SdkLogLevel is renamed to LogLevel and its values are now lowercase, such as "info". The All and None values are removed, and level: "silent" replaces None. SdkLogRecord is renamed to LogRecord, which has a new shape. Clients now print startup, readiness, connection loss and shutdown at Info by default. Set level: "warn" to print only problems. The Effect API's MessageCacheOptions<E, R, M> is now MessageCacheOptions<M>, because cache callback failures go to the client's onError rather than a cache-specific hook

  • abb5d3f: Add WebhookType and the IncomingWebhook, ChannelFollowerWebhook and UnknownWebhook shapes to both APIs. Received metadata now requires a kind and preserves future numeric kinds as type: "unknown" with rawType

    Bot 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 sourceGuild and sourceChannel snapshots reflect source visibility to the original creator, and their absence does not prove deletion

    Webhook metadata no longer carries tokens. The CreatedWebhook.webhook field and webhook-client fetch and edit results now use IncomingWebhook, 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 channels.deleteForumTag and a channels.edit with availableTags can, 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 as channels.create, channels.reorder or guilds.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 call client.cache.clear("channels") after the write

    Behavior change: A guildChannelUpdate event for a forum or media channel now removes the channel's cached posts that apply a tag missing from the update's availableTags, 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 update

    Behavior change: A guildChannelUpdate event 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 return

    Behavior change: A shard's first session now releases its communities' cached snapshots, as every later new session already did, and stops pending reads from storing them. Community, member, role, emoji, sticker and channel snapshots read through REST before connecting no longer outlast the first READY, because the events between that read and the session were never received. The release happens as READY arrives, before the communities Fluxer then sends again through 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 through sharding.sessions is not a new session and keeps them

    The new roles.getAll(guildId) method returns every cached role of a community, including everyone and ordered like roles.fetchAll, or undefined unless the cache knows the list is complete. A guildCreate event makes it complete, as does an uncontested roles.fetchAll read that finishes while the community's shard is connected, because only role events keep the list current. A full read that finishes while the community's shard is disconnected therefore does not make the list complete, while a list that was already complete stays complete through a recovery that resumes the session. Role create and update events keep it complete. Removing any of its roles, a new session for its shard and every successful role write by the bot, including create, edit and reorder, or one whose outcome is unknown, make it unknown until the next guildCreate or full read. The roles cache limit counts the roles of every community together, so a bot in many communities needs a larger maxEntries to keep complete lists

    The new members.getSelf(guildId) method returns the bot's own cached member of a community, or undefined when it is not cached or the bot's account ID is not yet known from READY or a members.fetchSelf result. With cache.members enabled, each guildCreate now stores the bot's own member. The guildCreate events 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. A guildCreate sent 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 channels

    The new ShardState.sessionEpoch field 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 through guildCreate. It is the shard's current value, so an event still waiting in a handler's queue can already see a newer one

    The new EventContext.sessionEpoch field gives the receiving shard's sessionEpoch when 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. Compare context.sessionEpoch per community, not per shard, because one shard serves many communities. Events delivered through the testing entry points carry the epoch of the test gateway's session in the same way

    The new context field of BotEventContext gives each runBot event handler, in both APIs and in bots run by createTestBot, the same EventContext that a client.on handler receives, so a runBot bot can compare context.sessionEpoch per community as well

  • 321d8bb: The commands namespace has two new helpers in both APIs, and the quoted parser and its rejections changed. The new commands.suggest(name, candidates) function returns the candidate closest to a misspelled name, using the rule behind the suggestion of CommandUnknownName: 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 returns undefined when 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 new commands.parseArgument(token, descriptor, options?) function converts one word with any built-in argument descriptor except custom, using the router's own grammar. It returns the descriptor's value type, undefined for a rejected word, and the descriptor's default or undefined for a missing one. The new CommandArgumentParseOptions type carries guildId, which member descriptors need. Invalid input to either helper, such as a bad descriptor, a non-string name or candidates that are not an iterable of strings, throws ConfigurationError in both APIs, like other registration misuse

    PrefixCommandParseRejection and the CommandParserRejected outcome of onUnmatched have an optional name, the command name the parser read before it declined, as typed. The commands.parseQuoted parser 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 defineBot to the default and Effect entry points. It returns a bot's runBot options 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 both runBot and createTestBot

    The starter bot in examples/starter now exports its options as bot with defineBot, answers !ping through the commands option instead of matching the message text, and starts only when Node.js runs the file. It now also answers !ping followed by more text or in other letter cases. The fluxerly init command writes these shipped files instead of its own copies, so the generated bot is the one the documentation shows, and its generated test imports bot. The repository examples, the quick start, the testing guide and the README use the same shape

  • 5717638: Behavior change: The default maxPages of an iteration now grows with maxItems, so a caller who sets only maxItems gets up to that many items. It is the pages maxItems needs at the page size plus 100 spare pages for short pages and skipped duplicates, instead of a fixed 100. For example, messages.iterateHistory with maxItems: 10000 and the default page size of 50 used to fail with PaginationError reason pageLimit after 5,000 items and now reads all 10,000. This applies to iterateHistory, members.iterate, members.iterateSearch, guilds.iterate, auditLogs.iterate, iterateReactionUsers, iteratePins, threads.iterateMembers and messages.iterateSearch, 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 pageLimit. An explicit maxPages is used as given, including a value above or below the new default, except that messages.iterateSearch never reads more than 400 pages. Code that relied on the old default to stop a long scan early can set maxPages: 100

    Behavior change: The directMessages.open and directMessages.send methods 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 as open("bad"), fails before the cache is touched and leaves it intact. Code that relied on the full clear can call client.cache.clear("directMessages") after the write

    The PaginationError hint for reason pageLimit now tells callers to set maxPages explicitly or lower maxItems, and to narrow the filters of a message search, which reads at most 400 pages. It no longer suggests a larger pageSize, because the default maxPages shrinks as pageSize grows

  • 321d8bb: Three additions to the shared errors object and the text helper, plus two fixes.

    The new errors.outcome(error) function returns whether a failed request may have reached Fluxer, as "notDispatched", "rejected" or "unknown". It reads the outcome of any SDK error that has one and, for an Effect Cause, the least certain outcome of its failures. It returns undefined when the SDK cannot establish an outcome, such as for CancelledError, SdkDefect, non-SDK values and a Cause with 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, including undefined, as a write that may have applied and reconcile before sending it again. A Cause covers only the failures it kept, and Effect's concurrent forEach and all drop 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 in Effect.timeout yields only a TimeoutError, so use the operation's timeoutMs option to keep the outcome.

    The new errors.isNotFound(error, resource?) function is true for an SDK error with HTTP status 404, including a 404 that messages.send reports with reason rejected. Pass a NotFoundResource such as "member" or "channel" to also require Fluxer's matching UNKNOWN_* code. A 404 says only that the resource is missing, not why: Fluxer answers UNKNOWN_CHANNEL for a missing channel and also for message and reaction requests in a thread from a bot that is no longer in the community, while other requests from such a bot get a 403 missingPermissions. A 404 without a recognized code is true without a resource and false with one. Any other resource name throws HelperError with operation errors.isNotFound and the new reason resource. The NotFoundResource type is exported from both entry points.

    The new text.truncate(content, { maxLength, suffix? }) function shortens text to at most maxLength UTF-16 units and ends it with suffix (default "…") when anything was cut. It never splits a surrogate pair and does not trim or look for word boundaries. Invalid text or options throw HelperError with operation text.truncate. The TextTruncateOptions type is exported from both entry points.

    ClientClosedError gains outcome, an addition. A REST call ended by a closing client now reports how far it got: notDispatched when the client was already closed or the request was still queued, rejected when closure came while waiting out a 429 before a retry, and unknown when the request was in flight. The outcome is also recorded in details.outcome, and an unknown outcome 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, keep outcome as undefined. 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 reports notDispatched for the message request even if the earlier step took effect. A closure that cuts off a read records details.read and gets no hint to check for an applied change. The POST reads messages.search, members.search and attachments.refreshUrls count as reads in a closure and in their other failures, so their network and timeout failures are now retryable by errors.isRetryable. An OAuth request that closure cuts off in flight still fails with OAuthError reason network and outcome unknown, and a closure during OAuth instance discovery now reports notDispatched. A message cleanup closed while a batch waits out a rate limit now reports that batch's rejected outcome instead of unknown, and a closure between batches already reported notDispatched. The constructor takes an optional { outcome, read } object, so existing new ClientClosedError() calls are unchanged.

    Partitioned inviteCreate handlers now order events per community. They previously ignored the invite's nested guild.id and channel.id, so every invite shared one key and ran one at a time. With partition: "guild", invites of different communities run side by side while one community's invites stay ordered, and a group invite without a community is keyed by its channel.

    The embed input documentation now states Fluxer's limit of 6,000 characters of combined embed text per message. The SDK does not check it locally, and exceeding it fails remotely with reason rejected and an invalidFormBody API error.

  • 78579fe: Add messages.fetchHasReaction to 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

  • b1b9d72: Fields that Fluxer documents on gateway events and the SDK used to drop now reach handlers, in both APIs

    A messageCreate event carries mentionedHere, channelType and nicknames, and the messageFields option accepts them. They are set only on a created message, so messageUpdate events, 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 as true. The channel type is Fluxer's number, kept as received, with 1 for a direct message and 3 for a group direct message. The nicknames of a group direct message are a frozen object keyed by user ID, like DirectMessageChannel.nicknames. A value of the wrong type rejects the created message, like mentionedEveryone

    A messageDelete event carries member, a new frozen DeletedMessageMember that is a GuildMember without username and isBot, 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 it

    Voice states from voiceStateUpdate, voiceStateSnapshot and the call events carry isE2eeCapable and isPeerToPeer, which read false when omitted, and version, which is absent when Fluxer sends none. Behavior change: A voice state whose e2ee_capable or p2p is not a boolean, or whose version is not a non-negative integer, is now rejected like the other malformed voice state fields

    A guildCreate event carries memberCount, onlineCount and joinedAt as 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, so guilds.fetchCounts remains the way to read current totals

    The testing entry points' WireMessage accepts optional channel_type, nicks and mention_here, so fixtures.message({ channel_type: 3 }) emits a created message in full

  • 8b70a5c: GuildFeatureToggles.VisitsEnabled lets guilds.edit keep community visits on. Fluxer now enables VISITS_ENABLED for new communities and removes every toggleable feature that a featureToggles list leaves out, so an edit without it turns visits off and ends current visits. Before this, the SDK rejected VISITS_ENABLED locally, so every featureToggles edit turned visits off

  • f0f217a: ResolvedInstance has a new discoveryEnabled flag, in both APIs, that reports whether the instance has community directory discovery switched on. Fluxer documents a missing flag as enabled, so it reads true when the instance omits it. While it is false, the discovery namespace's search, apply, edit and withdraw fail with reason rejected, HTTP 400 and details.providerCode DISCOVERY_DISABLED, while fetchStatus and fetchCategories still answer, and fetchStatus then reports eligible as false. The value is read once when the client resolves the instance. Behavior change: A discovery document whose features.discovery_enabled is present but not a boolean is now rejected as a protocol failure, like a malformed presigned_attachment_uploads

  • 7611ba6: Behavior change: messages.keepTyping now returns the task's own result when a later typing refresh fails. Before, the default API returned Err with 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's Cause. Now a refresh failure stops the refreshes without interrupting the task, and it logs one Warn record with the new code rest.typingRefreshStopped, whose fields.channelId names the channel and whose error holds 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 as SdkDefect in the default API and in the Cause in the Effect API

    Code that relied on the Err or the combined Cause to learn about a failed refresh should read the rest.typingRefreshStopped record from the logging sink instead.

    Before, client closure during the task also returned ClientClosedError (Err in the default API, Fail in the Effect API), and now the task's own result is returned. Code that detected shutdown that way should watch the client's state or observeState instead

  • c892c2c: Both Client interfaces have a new read-only lastReceipt property: The receipt of the most recently accepted event, as EventContext.receipt numbers 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 member field of messageCreate and messageUpdate messages, of messageReactionAdd and messageReactionRemove reactions, and of typingStart notices carries the author's or actor's GuildMember, 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, messageDelete events (Fluxer sends no username there) and batch or clear events. The messageFields option now accepts member, and the message cache keeps messages without it, so read it in the handler

    A 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.members enabled, these events also store their member in the member cache, whichever messageFields the client selects, so members.get answers for an author without a REST read. A member that cannot be read removes the account's cached member and logs gateway.memberSkipped, 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

    The testing entry points' WireMessage accepts an optional member without its user and guild ID, so fixtures.message({ member }) emits a decorated message in tests

  • b54fbd4: client.on("messageCreate", ...) handlers now run up to eight messages at a time by default in both the default and Effect APIs, matching runBot and the command router. Before, a createClient bot ran them one at a time, so one slow message handler stalled every later message, while a runBot bot did not. Handlers of other events still run one at a time unless partition is 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 permissions.fetchChannels(target, options?) method returns one member's permission bits in every channel of a community. Pass the bot's own ID from client.users.getSelf() as userId to 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 as permissions.calculate and permissions.fetch. The result is a frozen array of frozen ChannelPermissions entries ({ channel, permissions }), in the order Fluxer lists the channels

    Fluxer lists only the channels the bot can view, and no threads, so call permissions.fetch with a thread's ID for one. The options are the plain RequestOptions, without preferCache, because no cache holds a complete channel list. Failures use GuildOperationError with the new operation permissions.fetchChannels, and errors from the reads keep their own type. A listed channel that arrives without its permission overwrites fails as it does in permissions.fetch, with reason response, outcome "unknown", details.read and details.channelId

    The new permissionBits.refusedGrants(current, next, held) helper 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 answers missingPermissions for the rest. Pass the overwrite's current allow and deny as current and the new ones as next, with undefined for no overwrite, so a removal passes undefined as next. Pass the bot's permission bits in that channel as held. The helper returns 0n when held includes Administrator, and invalid input throws HelperError with operation permissionBits.refusedGrants

    The documentation of channels.create, edit, reorder, setPermissionOverwrite and removePermissionOverwrite now states this rule for each method. Their missing-permission hints name the channel that Fluxer checks and point to the helper

    The new permissionBits.sameOverwrites(first, second) helper 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, throws HelperError with operation permissionBits.sameOverwrites, because Fluxer rejects duplicate IDs on write

    The new errors.retryAfterMs(error) function returns how many milliseconds an SDK error says to wait before a retry, read from the retryAfterMs of any operation error or RateLimitError. For a ShardConnectionError it returns the wait of the failure that stopped its shard, and for an Effect Cause the longest wait among its failures. It returns undefined for an error without a usable wait and for any other value, and never throws

    The errors.isRetryable(error) function now also accepts an Effect Cause, as errors.outcome does. It is true only when the Cause has at least one reason and every reason is a retryable failure, so a defect, an interruption or an empty Cause gives false

    EventContext has a new receipt field 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 as guildCreate and its voiceStateSnapshot, get consecutive numbers in dispatch order. Handlers of one community can run out of order because of concurrency, partitions or partitionReady, so compare receipts to tell which event came first. Under a supervisor each child process numbers its own events

    A validation error with Fluxer's code INVALID_URL_FORMAT now has an SDK explanation in its details. Fluxer sends this code for a URL field it does not accept, such as text that is not an http or https URL, a URL with a user name or password, or a host without a top-level domain

    AuditLogActions.MemberRoleUpdate now documents the shape Fluxer records: A change with the key roles whose oldValue and newValue are the member's full role ID lists, so the added roles are those in newValue that oldValue lacks. A MemberUpdate entry that changes roles carries the same change

    TestDisconnectOptions has a new endSession option, default false, in both testing entry points. With endSession: true, the test gateway forgets the shard's session before disconnect closes 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's sessionEpoch increases, 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 throws ConfigurationError, and the native disconnect dies with it

    Without 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. TestDisconnectOptions.code now says this

  • 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 partitionReady option of client.on and of a runBot event 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 toward maxPendingMessages, maxPendingBytes and a draining shutdown. The option needs partition and otherwise fails with ConfigurationError whose field is "partitionReady", a new member of the ConfigurationError.field union

    Dropped events are also counted per event name. ClientCounters has the new eventsDroppedByEvent member, which counts overflow, closed and collector drops and malformed dispatches whose type names exactly one event, and the eventsDropped reason 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 metric fluxerly_events_dropped_total gains an event attribute where the event name is known, next to reason

    Behaviour changes to know about:

    • A partitioned handler with overflow: "dropOldest", which is the default for client.on and runBot, 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. Under maxPendingBytes the 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. Partitioned commands delivery follows the same rule. Handlers without a partition and the policies dropNewest and stop are 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
  • 989089a: Command routers can now wait for a partition's own setup before its commands run. The partitionReady option works for router.attach and for the commands option of runBot and createTestBot (BotCommandsOptions), in both APIs, with the same rules as the partitionReady option of client.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 needs partition and otherwise fails with ConfigurationError whose field is "partitionReady". A hook that fails reports each waiting message of that key to the onError option of the commands, and those messages run no command

    Behavior change for command routers that have an onError: A failing partitionReady hook, a throwing partition function and an overflow report of the router's subscription now reach that onError, in arrival order with the command failures. Before, a throwing partition function and an overflow report went to the client-level onError or the log, so a bot that replied from commands.onError stayed silent. A router without its own onError is unchanged

    In the Effect API the hook returns an Effect, and its required services are added to the requirements of runBot and createTestBot, which gain a trailing type parameter for them, and BotCommandsOptions gains 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.fetch method takes preferCache: true (PermissionFetchOptions) 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. The hierarchy.highestRole(member, roles) and hierarchy.canManageRole({ guild, actor, role, roles }) helpers (RoleManagementInput) check rank from held snapshots, and members.timeoutState(member, { now }) reports "none", "active" or "unknown" from communicationDisabledUntil. The permissionBits.patchOverwrite(current, { allow, deny, inherit }) helper (OverwritePatch) changes named permissions of an overwrite and keeps every other bit, and permissionBits.label(name) returns Fluxer's English name for a permission

    Behaviour changes to know about:

    • Missing-permission hints and the requirePermissions guard 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. The details.requiredPermissions field still holds Permissions keys
    • The requirePermissions guard now decides through permissions.fetch with preferCache, 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.fetch with reason response on 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, records details.read, details.channelId and details.responseField, and carries a hint. A cached channel without the list is fetched again. With preferCache, a closing client now fails a cache lookup with ClientClosedError outcome "notDispatched", as fresh reads do
    • The channels.removePermissionOverwrite comment now says that Fluxer requires ManageRoles, and a bot without Administrator can remove only an overwrite whose denied permissions it has all of
  • c892c2c: Handlers of an update or delete event now receive the cached value from before the change, as previous on the EventContext that client.on handlers, runBot handlers and event middleware receive in both APIs, so edit and deletion logs can show what changed. The messageUpdate, messageDelete and messageDeleteBulk events read the message cache, guildMemberUpdate and guildMemberRemove the member cache, guildRoleUpdate and guildRoleDelete the role cache, guildChannelUpdate, guildChannelDelete, threadUpdate and threadDelete the channel cache, and guildUpdate and guildDelete the community cache. The snapshot is the frozen value the client's own cache held just before it applied the event, never fetched. A messageDeleteBulk snapshot is a frozen list of the messages the cache held, once each in the order of the event's ids, because Fluxer sends only IDs for a bulk deletion. It is undefined when 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, or undefined where 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, and maxPendingBytes does not count it. The test clients deliver it the same way when their cache option is enabled

    The new PreviousSnapshots interface lists the snapshot type per event, and the new PreviousSnapshot<K, M> type gives the type of previous for one event. EventContext now takes optional type parameters for the event and message type, and EventInvocationOf.context, BotEventContext.context and the client.on handler's context are typed for their event. A helper that takes a plain EventContext still accepts any handler's context, including one from a client with messageFields, and a context built by hand, such as in a unit test, can leave previous out

  • ab97759: The ratelimit.wait and ratelimit.deadline log records and the rateLimit observation now repeat what a 429 response sent about the limiter that answered it. Log records carry these values in fields, and RateLimitObservation has them as optional members: scope, bucket, limit, remaining and resetAfterSeconds from the X-RateLimit-Scope, X-RateLimit-Bucket, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset-After headers, retryAfterHeaderSeconds from a numeric Retry-After header, and retryAfterSeconds and bodyGlobal from the body's retry_after and global members. Each value is present only when the response sent it in the expected form. A ratelimit.deadline record for a request refused before sending, behind a known pause, has none. Waits, retries and the existing global value are unchanged

  • d63165f: Roles can have icons. GuildRole now has icon, the image's hash, next to unicodeEmoji, and assets.roleIcon builds the image's URL. RoleCreate and RoleEdit take icon, an image data URI, and unicodeEmoji, where null removes the icon and setting one replaces the other. RoleCreate also takes iconSourceRoleId to 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.split and the pages of router.help now cut at a grapheme boundary, as Intl.Segmenter defines 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, text.truncate("aaaaaaaaa🇩🇪", { maxLength: 11, suffix: "" }) returned "aaaaaaaaa🇩", half a flag, and now returns "aaaaaaaaa". A piece or truncated result can now be shorter than maxLength and never longer, and the newline and whitespace preferences of text.split only choose positions between characters

    text.truncate keeps whole characters only. When the first character does not fit before the suffix, it returns the suffix alone, so text.truncate("🇩🇪x", { maxLength: 4 }) now returns "…" instead of half a flag, and a letter with more combining marks than fit is dropped entirely. text.split still cuts a single character that is longer than the limit between code points, because it must advance, and it still throws HelperError when the limit cannot hold one code point. Joining the pieces of text.split still reconstructs the input exactly, except that each lone surrogate becomes U+FFFD

    No code change is needed unless a caller relied on a cut inside a character, or on a result of exactly maxLength - suffix.length units of text

  • 419fb51: The text.truncate and text.split helpers now replace each lone surrogate with U+FFFD (�) instead of throwing HelperError. Ordinary code can create one, for example slice can 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, so maxLength still holds, and text.split pieces join to content.toWellFormed(). A code path that relied on the error can check content.isWellFormed() first. A suffix that is not well-formed still throws, and so does text that is not a string

  • a47b104: The package installs a fluxerly command. Running npx --no fluxerly agents, pnpm exec fluxerly agents or bunx --no-install fluxerly agents in an application's folder copies the SDK's rules for coding agents into its AGENTS.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 file

    The agent guide at agents/AGENTS.md starts 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.fetchFollowerStats, messages.publish and messages.fetchCrosspostSource to 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

    The errors.apiCode helper now names announcement, follow, conversion, publishing and published-message editing rejections, such as invalidFollowTargetChannel

  • c4f4f49: Add ownerId to the result of client.application.fetch() in 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 BotApplicationOperationError and reason "response". guards.ownerOnly() called without IDs now allows only that owner. The first invocation reads the owner with application.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 an undefined argument still throws ConfigurationError

  • 7733b23: A client logs one lifecycle.connected record once every shard it owns is connected, such as Connected to Fluxer as MyBot in 3 communities. When the bot owns every shard and is in no community, the record is a Warn that includes the bot's installation link

    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, onReady or connected points to the connected record, the setup option of runBot and client.connect()
    • An unsupported command argument type suggests the supported type it most likely meant, such as text for string, and lists every supported type
    • A missing bot token mentions node --env-file=.env bot.js, the current folder and a .env file saved as .env.txt
    • A command definition placed next to prefix in the commands option of runBot is explained as belonging in the inner commands object, an unknown key there lists commands and onError among the supported keys, and a missing prefix is reported as required
  • 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 client.cache.onChange method 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 concurrency unfinished calls, 256 by default, and up to maxPendingChanges later changes, also 256 by default, wait in the order they were applied. A change arriving beyond that drops the oldest waiting change, logs a cache.changesDropped Warn with the observer's new id and how many changes it has dropped, and counts the drop in diagnostics().counters.cacheChangesDropped. Both limits go in the new optional second argument, 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, throws ConfigurationError in 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 client.cache.onChange(listener) to observe changes to the client's local caches as { kind, op, key } records, where op is set, delete or clear. Every cache kind reports stored and replaced entries, removals including expiry and capacity eviction, and whole-kind clears from cache.clear(), connection gaps of unknown scope and shutdown. Keys use decimal IDs, such as channelId:messageId for messages and guildId:userId for 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 client.diagnostics().caches now keeps running totals since the client was created, in both APIs. The hits and misses values count local lookups such as client.messages.get, including lookups the SDK makes itself, and the evictions value counts entries the cache removed on its own, with capacity for room within maxEntries or maxBytes and expiry for entries older than maxAgeMs. Removal by events, writes, cache.delete, clears, connection gaps or shutdown is not counted, and a disabled category reports zero

  • 7755411: The client.cache.clear(kind) method now releases a single cache kind, and the new client.cache.delete(kind, key) removes one entry by the key format that cache.onChange reports, such as guildId:userId for members or channelId:messageId for messages. Both work in the default and Effect APIs, run synchronously and report a clear or delete change to cache.onChange listeners. Reads already in flight cannot restore what was removed. An invalid kind or key throws ConfigurationError, whose field is kind or the new value key

  • 7733b23: Add the callCreate, callUpdate, callDelete and entranceSoundPlay events for Fluxer's CALL_CREATE, CALL_UPDATE, CALL_DELETE and ENTRANCE_SOUND_PLAY dispatches, with frozen CallCreate, CallUpdate, CallDelete and EntranceSoundPlay payloads. Call participants use CallVoiceState, 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 guildId on messageDelete, messageDeleteBulk and channelPinsUpdate payloads 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|effect option 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 a package.json with start and test scripts that run without a build step, the bot with the starter's !ping handler, a test that talks to the bot through say from @neontechspace/fluxerly/testing or, for Effect, @neontechspace/fluxerly/effect/testing, .env.example and its copy .env, a .gitignore for .env and node_modules, and the AGENTS.md that fluxerly agents creates. The TypeScript and Effect starters write bot.ts and bot.test.ts, which Node.js runs directly, and a strict tsconfig.json for editors and a check script that runs the compiler. It replaces no file. When any of these files other than .env and .gitignore exists, it lists them and writes nothing. An existing .env is kept, and an existing .gitignore gets only its missing node_modules and .env lines. 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-install option 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 observe client option to export metrics and traces from either entry point to any monitoring system. The observer receives one frozen Observation for 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 in diagnostics().counters.sinkFailures without changing SDK work. The Effect API keeps its Effect metrics and spans and calls the same observer

  • cae766a: The new client.schedule method runs a task once after delayMs or repeatedly with intervalMs between runs, owned by the client's lifetime in both APIs, so a runBot command, handler or setup can start delayed or repeating work without keeping a command slot busy or leaving a timer behind. It returns a ScheduledTask whose close() cancels later runs. The delayMs value is 0 through 2,147,483,647 and intervalMs 1 through 2,147,483,647, and a repeating task first runs after intervalMs unless delayMs is set. Invalid options throw ConfigurationError in 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-level onError, or logged at Error with the new code lifecycle.taskFailed under the new failure kind task, and a repeating task keeps its schedule after a failure. A draining shutdown, including a requested runBot stop, lets running tasks finish within drainMs and counts them in the drain records' new tasks field, and every shutdown cancels the rest, counting the tasks it cut short in one lifecycle.tasksCancelled Info record. The test kit's idle() and say in 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 throws ClientClosedError in the default API and dies with it in the Effect API. The test clients treat a failed task run without an onError hook 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 a help command works in the keyed commands object of runBot without 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 match router.help, and the new CommandContextHelpOptions type describes them. For example: help: { execute: ({ help, reply }) => reply(help()[0] ?? "No commands") }

  • ce65b67: The client.logging.configure method now accepts debug in both APIs, so Debug output can be changed while the client runs, including output turned on by FLUXERLY_DEBUG. A debug value replaces the current Debug categories: true for every category, a list for those categories, and false or an empty list for none. An omitted debug keeps the current categories, while an omitted level or categories still returns to its default. LogLevelSettings gains the debug setting

  • 7733b23: Make errors and log records say what went wrong and what is accepted. HelperError and AssetUrlError messages name the helper and the accepted form, for example Helper operation format.userMention failed: IDs must be decimal strings from 0 through 9,223,372,036,854,775,807, instead of Invalid input for format.userMention. PaginationError messages include the input explanation or what stopped the iteration, and the error carries a hint: The rejected path for invalid settings, raising maxPages for pageLimit, and retrying later for cursorStalled and indexing

    Input validation explanations name the field in words and state the accepted range with its unit, such as ms, bytes or seconds. Presence explanations name the field they describe, unsupported-field explanations list the accepted fields or the type that defines them, timeouts state their ms unit, and ban and timeout durations state their range in ms with the matching days or years. Connection, startup and shutdown records use full sentences, such as Shard 1 lost its connection. Close code 4000 (UNKNOWN_ERROR): The gateway reported an unspecified error. Reconnecting and resuming the session in 486 ms, and configuration errors name the full option path, such as connection.startupTimeoutMs. SupervisorError and SupervisorChildError messages describe the failure and carry a hint. An instance discovery failure keeps its transport error code and says what was wrong with the response. CancelledError accepts an optional operation, which is recorded as details.operation and named in the message, and its hint explains that signals the SDK passes to handlers abort when the client shuts down

    The inputValidation.path and constraint values are unchanged. Messages remain changeable text, so match on code, reason, field and inputValidation and on log record codes instead

  • 7733b23: Provide a native client as an Effect service with FluxerClient from the Effect entry point. FluxerClient.layer(options) creates the client in the layer's scope and shuts it down when that scope closes. FluxerClient.layerConfig({ token: Config.Redacted("FLUXER_BOT_TOKEN") }) reads the token, and optionally any other top-level setting, from Effect Config. The token stays redacted until the client receives it. A missing Config value fails the layer with ConfigError, while a blank token or another invalid setting dies with ConfigurationError. Building either layer does not connect. Register handlers, then call client.connect() or client.run()

  • 7733b23: Add FluxerTestClient to @neontechspace/fluxerly/effect/testing for applications that read their client from the FluxerClient service. FluxerTestClient.layer(options) creates a test client with createTestClient and provides its client as FluxerClient and the whole test client, with its controls, as FluxerTestClient. Application code then runs against the in-memory Fluxer unchanged. Closing the layer's Scope shuts the client down and dies with UnhandledTestFailuresError for a handler failure the test did not read

  • 7733b23: Read the community that owns a custom emoji or sticker with emojis.fetchSource(id) and stickers.fetchSource(id) in both entry points. Fluxer calls a community a guild. The result is a frozen ExpressionSourceGuild with the community's id, name, icon hash and badge features. Fluxer answers when the source community is discoverable or the bot is a member of it. A private or unavailable source community fails with GuildOperationError whose apiError.code is unknownResource. Badge names added by Fluxer later are kept rather than failing the read, and a response missing any documented field fails with reason response

  • 7733b23: The members.fetchCanManage method accepts actorUserId to 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 reason input before any request. Both entry points name the option type CanManageOptions

  • 314ac29: Add fileSessionStore(directory) to both entry points, a built-in SessionStore for sharding.sessions that 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 guildHealthUpdate event with a frozen GuildHealthUpdate payload, { guildId, degraded }, 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 it

  • 1afd073: Add GuildCreate.isNewJoin to distinguish provider-reported joins from startup and recovery availability snapshots through both APIs. Existing guildCreate delivery and connect() readiness remain unchanged

    Classification 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_REQUIRED rejection 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 with apiError.code set to twoFactorRequired and apiError.providerCode set to TWO_FACTOR_REQUIRED, instead of apiError set to null. 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 optional mfaLevel field with the new GuildMfaLevels constants None and Elevated, so a bot can see this requirement before it sends a moderation request. The field appears on fetched communities, community list entries and guildCreate and guildUpdate events. A community whose mfa_level is present but not 0 or 1 is treated as malformed, like other malformed community fields

    The moderation and message deletion references describe this requirement, and the messages.deleteMany reference states that it requires ManageMessages even for the bot's own messages

  • 7733b23: Add snowflakes.shardFor(guildId, totalShards), which returns the shard Fluxer routes a community's events to, and client.shardIdForGuild(guildId), which returns this client's shard for a community or undefined when another process owns it. Fluxer calls a community a guild. Both APIs provide them

    For an invalid ID, the helper throws HelperError with reason id, and for a total outside 1 through 16,384 with the new reason shardCount. The client method throws ConfigurationError for an invalid ID

  • 7733b23: Pass an EventContext with shardId and receivedBytes to every client.on handler, 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 it

    Add client.use(middleware) to run event middleware around every later on handler invocation, including those of existing subscriptions, command routers and runBot handlers. Middleware runs in registration order, receives the event name, payload, context and subscription ID, and can skip the handler by not calling next. Failures of middleware and of the handler inside it are reported like handler failures, so middleware cannot hide a handler failure. Both APIs return a MiddlewareRegistration with close(). The default registration also works with using, and the Effect API ends the registration when the executing Scope closes and runs middleware with the services available at registration

  • 7733b23: Add the partition handler option to client.on and runBot events, 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 to concurrency, 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.ignoredEvents, gateway.flags and gateway.presence, sent in each new session's Identify. The ignoredEvents option 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 a raw subscriber disables automatic suppression. Setting flags: { debounceMessageReactions: true } turns on Fluxer's DEBOUNCE_MESSAGE_REACTIONS flag, merging direct-message reaction runs into messageReactionAddMany. The presence option sets the initial presence, which Identify carries and the SDK publishes after READY as if presence.set had been called before connecting. An explicit ignoredEvents list that names a type the SDK or an enabled cache needs fails client creation with ConfigurationError. GatewayOptions is a new public type

  • 7733b23: Expose the instance's advertised web domain migration as domainMigration on ResolvedInstance, typed as the new InstanceDomainMigration, and log an Info record when a migration is enabled

  • 314ac29: Add client.logging.configure({ level, categories }), 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 debug setting and FLUXERLY_DEBUG still 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 new LogLevelSettings type describes the accepted settings

  • abb5d3f: messages.delete, messages.deleteMany, messages.pin and messages.unpin accept an auditReason option 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 EmbedBuilder wherever they accept embeds, in both entry points. A string such as client.messages.send(channelId, "Hello") is shorthand for { content: "Hello" } in messages.send, messages.reply, messages.edit, directMessages.send and a webhook client's send and editMessage. An EmbedBuilder in embeds is built once when the operation reads its input, so client.messages.reply(message, { embeds: [builders.embed().title("Status")] }) needs no build() call and later changes to the builder do not affect that operation

  • 7733b23: Send text-to-speech messages with tts: true on messages.send, messages.reply, directMessages.send and the runBot reply helpers. A non-boolean value fails with reason input before 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 accept tts, because Fluxer never sends a webhook message as text-to-speech. Webhook sends, replies and forwards with tts fail with reason input before any request. Messages gain an optional tts field, selectable through messageFields. Fluxer does not store the value, so it is meaningful only on the message returned by a send and on messageCreate events. Fetches, history pages and other later reads always report false. A message whose tts is present but not a boolean is treated as malformed, like other malformed message fields

  • 7733b23: Add MessageType constants to both entry points, naming the values of Message.type, for example message.type === MessageType.UserJoin. The constants are Default, RecipientAdd, RecipientRemove, Call, ChannelNameChange, ChannelIconChange, ChannelPinnedMessage, UserJoin, ChannelFollowAdd, ThreadCreated, Reply and ThreadStarterMessage. Message stays 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 in Message.type

  • d3a22bc: A missing-permission rejection now names the permissions the failed operation needs, in both entry points. When Fluxer answers HTTP 403 with MISSING_PERMISSIONS, guild, channel, message, send and webhook operation errors list them in details.requiredPermissions as Permissions keys, such as ["ViewChannel", "ManageChannels"], 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, and rest.request keep a general hint without details.requiredPermissions. Operations without an entry that previously received the generic message-sending or moderation hint now receive the general hint instead

  • 7ec55f6: PresenceUpdate, and so each entry of PresenceUpdateBulk and of the presences in a member chunk, now carries customStatus in both entry points, instead of dropping the custom status Fluxer sends. The value is null when the account has none, which includes an offline or invisible account because Fluxer hides the status of one, and otherwise an ObservedCustomStatus with text, emoji and expiresAt. The emoji field is null or an ObservedCustomStatusEmoji with id, name and animated, so a custom emoji keeps its ID and animation flag and a Unicode emoji has a name and no ID and always reads as animated: false. Fluxer stores custom statuses without validating them, so a field of the wrong type reads as null and the presence is still delivered

  • 7733b23: Add the raw event, which delivers every gateway dispatch a shard accepts as { shardId, t, s, d }, including READY, RESUMED and 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 code events.raw, and nothing is built while no raw subscriber exists. Types listed in gateway.ignoredEvents never arrive

    Add client.gateway.send(shardId, op, d) for gateway commands without an SDK method. It fails with GatewaySendError whose reason is input for invalid arguments or frames above Fluxer's 4,096-byte limit, reserved for heartbeat, Identify, Resume and server-only opcodes, notOwned, notReady or busy, and with ClientClosedError after shutdown. A throw from a getter or toJSON in the command data rejects with SdkDefect code application.defect, and cycles, BigInt values and overly deep data fail with reason input. Completion means the frame was handed to the connection. Each sent command is logged at Debug with code gateway.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, or client.messages.paginate(channelId, pages, { users }) elsewhere, in both the default and Effect APIs. Pages are strings or { content, embeds } objects. Adding or removing an arrow both count as a click, so the bot needs only Add Reactions and Read Message History, and removeClicks: true instead removes each click, which needs Manage Messages. Listening stops after idleMs (default 60,000) without a click and at most after timeoutMs (default 300,000), then the bot removes its own arrows and the current page stays. The result reports idle, timeout or singlePage, and failures and cancellation use the usual failure channel. The new ctx.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, gateway.ignoredEvents: "auto" now always keeps MESSAGE_REACTION_ADD and MESSAGE_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, LinkHelpers and AssetHelpers for the helper objects, whose tryParse methods return a Result in the default entry point and an Effect in the Effect entry point, and Operation, MessageContent, EditMessageOptions, WebhookMessageOptions, AuditLogFilter, AuditLogQueryBase, AuditLogIterationQueryBase, CommandArgumentOptionalValue and ErrorMatchHandlers. Both entry points add Commands and PrefixCommandBatch, each with its own API's shape, and the Effect entry point adds BatchRequirements, CommandRequirements, EffectRequirements, BotEventServices and HandlerServices

    Every 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 maxAgeMs callback, as the messages cache 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, null for no age limit or 0 to skip retention, for example channels: { maxAgeMs: (channel) => (watched.has(channel.guildId) ? null : 0) }. A callback that throws or returns an invalid value removes the older copy and is reported as a cache failure to the client-level onError, or logged at Error, without changing the event or REST result. ResourceCacheSettings now takes the snapshot type as a type parameter, and ClientOptions.cache passes the matching type for each kind

  • 7733b23: Add client.rest.request({ method, path, query?, body?, files?, auditReason?, timeoutMs? }) for Fluxer API routes without an SDK method. Default calls return a ResultAsync and accept signal, and Effect calls return an Effect. Both resolve with { status, headers, body }, where header names are lower case and body is the parsed JSON body or undefined for 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 /v1 prefix. Token-authenticated webhook routes are rejected because their credential is part of the path. Invalid input fails with RestRequestError reason input before any request, a non-2xx status fails with RestRequestError carrying the status and sanitized apiError, and a request after shutdown fails with ClientClosedError. Logs, metrics and spans show only a route template in which IDs, codes and other non-word segments are placeholders. The files field accepts the message attachment inputs and sends them inline as multipart form data with their metadata in payload_json

  • 7733b23: Add the rest client option to tune REST scheduling: The concurrency setting for Fluxer API requests that run at once (1 through 64), mediaConcurrency for attachment downloads (1 through 64, default 4), maxQueued (1 through 65,536, default 256), queuedJsonMaxBytes (64 KiB through 256 MiB, default 4 MiB) and defaultTimeoutMs (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 when sharding: "auto" sizes or enlarges it. An explicit concurrency is used as given. The defaultTimeoutMs setting replaces the fixed 30,000 ms deadline of REST operations and attachment downloads that omit timeoutMs. Invalid values throw ConfigurationError naming the field in the default API and are defects in the Effect API, and client.diagnostics().rest reports the configured capacities

    A request that fails with reason busy because the REST queue or the upload byte budget is full now logs rest.busy at Warn, at most once a minute, naming the full limit. Every such failure is counted in the new diagnostics().counters.restBusy counter

  • 7733b23: A shard that resumes a session from the sharding.sessions store 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 code lifecycle.cacheRefill, at Info when it succeeds and at Warn when some communities fail or it stops after 10 failures

    Set sharding.refillCaches to false to skip the refill

  • 37612fd: The runBot commands option now accepts the delivery settings of the router's attach in both entry points: concurrency, partition, overflow, maxPendingMessages and maxPendingBytes. They reach the router unchanged and are checked before any client exists, so an invalid value throws ConfigurationError in the default API and dies with it in the Effect API. Previously runBot passed only onError, so its commands always ran eight at a time and dropped the oldest waiting message when 256 were waiting. A command router set to overflow: "stop" stops the bot when its queue fills, like a handler. The commands guide shows the settings

  • dd52acd: The default API's runBot passes its setup callback a second argument, { signal }. The AbortSignal aborts 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 in setup when it aborts, so that work ends with the bot. The runner does not track or await such work. In the default createTestBot, the signal aborts when the test bot's shutdown starts

    The Effect API's setup is 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", or totalShards: "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 code 4011 (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 own connection.startupTimeoutMs deadline, so a slow count does not shorten the time the shards get to connect. Until the count completes, client.shards is 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 code 4011, 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 code lifecycle.resharded and the old and new totals. After 3 moves within an hour, or at 16,384 shards, a further 4011 closure ends the client with a ConnectionError, and the lifecycle.connectionEnded record says why. An explicit totalShards still ends the client on the first 4011 closure. An application can pass sharding.identify.permit(shardId, totalShards, signal) to 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 sharding.sessions store loads a saved { sessionId, sequence, resumeUrl, savedAt, totalShards } snapshot 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. The connection.recovery option sets minDelayMs, maxDelayMs, attemptTimeoutMs and healthyResetMs for established-session recovery, with the previous 1,000, 30,000, 30,000 and 60,000 ms as defaults, and the Effect API also accepts a schedule that decides recovery delays and ends recovery when it completes. ConnectionRecoveryOptions, IdentifyCoordinator, SessionStore and SessionSnapshot are 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.discarded Info record with the count in fields.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 one events.refused Info record with the count in fields.refused. All of these events are also counted in the new diagnostics().counters.eventsDropped.closed counter. A subscription stopped by overflow: "stop" keeps its existing events.overflow record

  • ada1d09: Add an optional runBot runner to both SDK entry points for critical subscriptions, graceful shutdown and opt-in process signal handling

    Configure 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 SdkDefect for defects

  • 7733b23: The token option of supervisor.child.run accepts string | undefined in both entry points, as createClient and runBot do, so process.env.FLUXER_BOT_TOKEN can be passed directly. A missing or blank token still fails child.run with ConfigurationError once 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 new processes and shardsPerProcess options split every shard into contiguous blocks, one per child, named process-0, process-1 and so on. With totalShards: "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 with sharding: "auto" does. The count needs the new token supervisor option and accepts instance and transport for a self-hosted instance or a proxy. It logs supervisor.automaticSharding, and a failed count fails start with the new SupervisorError reason shardCount, whose cause is the count's error. When Fluxer later closes a child's shard with close code 4011 because the bot outgrew the plan, the supervisor stops every child, counts again and starts children for a larger plan, logging a supervisor.resharded Warn. After 3 such moves within an hour, or at 16,384 shards, it fails with reason closed instead. The identify.coordinator supervisor option accepts the same IdentifyCoordinator a client accepts in sharding.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 code supervisor.identifyPermitFailed while the child retries like after a network failure. Supervised children can keep resumable sessions across a deploy with clientOptions.sharding.sessions in supervisor.child.run. A shard plan or identify coordinator in that sharding object is rejected with ConfigurationError, 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 every diagnosticsIntervalMs, 5,000 ms by default. The status() method shows the latest snapshot in the new diagnostics field of each child. SupervisorChildDiagnostics and SupervisorChildSharding are new public types. ConfigurationError can name the new fields processes, shardsPerProcess and diagnosticsIntervalMs, and code that switches over every SupervisorError reason needs a case for shardCount

  • 314ac29: Add say(content, options?) to the test bot of both testing entry points. It delivers a message from a human user, waits until the bot settles as idle does, and returns the messages the bot sent in response in wire shape, or an empty list when it sent nothing. The default createTestBot now returns the new TestBot type, and the new TestSayOptions type adds message overrides to the wait options

  • 7733b23: Add @neontechspace/fluxerly/testing and @neontechspace/fluxerly/effect/testing for application tests that need no network, token or Fluxer account. The createTestClient function returns a real client wired through the transport option to an in-memory Fluxer, together with controls: The ready() control connects through a protocol-v1 fake gateway, emit(type, payload, { shardId }) delivers a wire dispatch such as MESSAGE_CREATE through the SDK's own decoders, caches and handlers, and disconnect({ shardId, code }) closes a shard's connection so the client resumes, or stops for a fatal close code. The user and heartbeatIntervalMs settings choose the bot account reported in READY and the HELLO heartbeat interval. The rest.respond(matcher, response | handler) control answers HTTP requests matched by a "METHOD /path/:param" pattern, a RegExp or a { method, path } object, requests() and commands() record what the client sent without its token or Authorization header, logs() captures log records at the configured level while still calling application sinks, and counters() reads the diagnostics counters. Message sends and edits without a matching rest.respond registration receive an echoed bot message. Other unmatched requests receive a Fluxer-shaped 404 and a testing.unmatchedRequest Warn record. Register an explicit failure response to test a rejected message send or edit. The native createTestClient is scoped: Closing its Scope shuts the client down and closes the transport, while the default client uses shutdown() or await using. Both entry points share fixtures, createFixtures and fixtureToken: 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 author

    The createTestBot function tests a bot written for runBot as written. It takes the bot's own options object, registers its events and commands with the same handler contexts, delivery defaults and command router as runBot, and runs setup once in ready before connecting. The signal, processSignals, reportFailure and drainMs settings are accepted and ignored, and an undefined token uses the fixture token. A misspelled or unsupported option key throws ConfigurationError with a hint naming the closest key that createTestClient or createTestBot accepts, as createClient does. A failed setup rejects ready with ApplicationError. The result is the usual test client, typed TestBot in the Effect entry point

    Tests 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. The idle() 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 with TestTimeoutError after timeoutMs when 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 with vi.useFakeTimers(). The SDK runs on setImmediate, so fake timers must leave it real, and idle() rejects with ConfigurationError, or dies with it in the native entry point, instead of settling while no handler can run

    A 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 onError hook was registered. The default shutdown() and await using disposal reject with UnhandledTestFailuresError, which lists the Error records of those failures. The native 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 the failures() control, which marks it as expected, or handles it with onError

  • 7733b23: Add the advanced transport client option. Its fetch and webSocket settings replace the HTTP and WebSocket implementations, for example for proxies, instrumentation or tests, and userAgent replaces the User-Agent header. Replacements receive every client request, including instance discovery, REST, signed upload parts, attachment downloads and gateway connections. Invalid values throw ConfigurationError naming the field in the default API and are defects in the Effect API. Bot, webhook and OAuth clients now send User-Agent: Fluxerly.js/<version> (+https://fluxerly.neontechspace.com) with every HTTP request and gateway handshake. A configured transport.userAgent replaces it for a bot client. Custom WebSocket factories receive the handshake headers, including the User-Agent, in WebSocketOptions.headers

  • 1eeb268: A client created without sharding settings now keeps running when the bot outgrows one shard. Fluxer refuses a new session with close code 4011 (sharding required) once one shard would hold more than 2,500 communities, and such a client previously ended with a ConnectionError, 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 as sharding: "auto" does, with the same limit of 3 moves within an hour, and logs lifecycle.resharded at 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, which sharding: "auto" avoids. An explicit totalShards, including 1, still ends the client on 4011, and supervised children are unchanged because the supervisor always assigns an explicit plan

  • dd52acd: Add client.users.getSelf() to 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 User directly in the default API and a never-failing Effect in the Effect API, like users.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 use users.fetchSelf for a current remote snapshot

  • 2b86017: MessageFlags gains VoiceMessage (8192), which marks a voice message in Fluxer. Message and webhook send and edit operations still reject this bit before dispatch, because only SuppressEmbeds and SuppressNotifications are writable in this SDK

  • a4c3572: VoiceState, which voiceStateUpdate and voiceStateSnapshot deliver, now reports isSelfVideoOn, isSelfStreaming, viewerStreamKeys and member. The first three also appear on CallVoiceState, while member does not, because Fluxer attaches no community member in a private call. The member field is the same GuildMember shape 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 nonce in 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.md now maps common bot tasks, such as sending messages, moderating members, checking permissions and testing, to the declaration file and members that cover them, instead of pointing agents at the re-export file dist/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 section fluxerly agents writes 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.concurrency and rest.mediaConcurrency options change these numbers. Effect API users, direct messages, webhooks and application calls use the client's rest.defaultTimeoutMs, not a fixed 30 seconds. The users.fetchSelf and directMessages.open comments explain when a result is not cached, and the users.fetch and directMessages.fetch comments state the 256-ID exception

  • 5e98527: A native on, messages.collect or messages.collectReactions registered 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

  • 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 messages.deleteOwnMessages comments 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

  • 8b70a5c: The missing-permission hint for messages.addReaction now 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 .env file that fluxerly init creates for the bot token is now readable only by its owner on Linux and macOS, with mode 0600, so other local accounts cannot read the token. An existing .env keeps 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 reason input and a message that explains the fix. Before, Fluxer answered it with an HTTP 503 error page and stored nothing, which looked like an outage. This covers every request the SDK encodes, such as message content, embeds and channel topics. The text.truncate and text.split helpers and toWellFormed() clean such text, and client.rest.request still sends its body as given

  • 051a969: The members.move comments now explain a refusal Fluxer added. Moving a member into an occupied voice channel whose participants all use peer-to-peer voice leaves the member in its current channel and fails with reason rejected, HTTP 400 and details.providerCode USER_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.run helper now succeeds when the parent stops the child before its gateway session is ready, as the default API helper already did. It previously failed with ClientClosedError after 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 nightly npm tag twice a day when the SDK changed, with a version such as 1000.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 --exact with Bun.

  • 08747b6: The rest.rejected Warn record logged for a refused permission overwrite write now carries a precise fields.hint. Before, a 403 MISSING_PERMISSIONS on setting or removing a channel's permission overwrite logged the general missing-permission hint. Now the hint is the one that channels.setPermissionOverwrite and channels.removePermissionOverwrite already 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 permissionBits.refusedGrants helper. No code change is needed

  • e5e7d10: Fixes for the guild cache, command replies, retry classification and message cleanup. A successful discovery.apply, discovery.edit or guilds.editVanityUrl call no longer stores its reply as a cached guild, so guilds.get and guild enumeration no longer return an application or vanity object after one of these writes. These writes now only remove the cached guild

    The MessageCleanupError from a preview whose filter threw now keeps the thrown value as cause, as documented

    With 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 seconds

    The errors.isRetryable function now returns false for a ConnectionError with reason network and 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 retryable

    The default API client.observeState now throws ConfigurationError for a listener that is not a function, instead of logging an observer failure on every state change

    The CommandArgumentMetadata comments no longer give user, which registration rejects, as a type example, and say that a custom argument can also use rest

  • e5e7d10: A session saved by sharding.sessions is now resumed at most once. Before, a restart that resumed a saved session and then crashed left the old snapshot in the store, so a restart within 60 seconds of the original shutdown could resume it again and receive the same dispatches a second time, including any dispatch that caused the crash. Before resuming, startup now saves a copy of the loaded snapshot with an empty sessionId. A later load of that copy starts a new session and logs lifecycle.sessionSnapshotIgnored at Warn, until the next shutdown saves a resumable session again. Custom stores receive this extra save call at startup. If it fails, the shard logs lifecycle.sessionSaveFailed and starts a new session instead of resuming

    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 duration argument 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 in diagnostics().counters.cleanupFailures. Its final record previously said the shutdown completed and the counter stayed at 0

  • 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 init command's guide link now point to the website at https://fluxerly.neontechspace.com.

  • 963f0be: The comments on GuildBan.moderatorId and AuditLogEntry.userId now 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 a GuildUpdate entry. Both IDs already decoded, so no behavior changes

  • e5e7d10: With the default childOutput of prefix, a supervisor now reads each child record and prints it in the format of the stream it goes to. SDK records print as readable labeled lines on a pretty stream, and as JSON Lines with a fields.child entry on a json stream. For this, default-API children always log json, as do Effect API children whose logging.format is json. Setting FLUXERLY_LOG_FORMAT or FLUXERLY_LOG_COLOR in the child environment still takes precedence

    Before, starting a bot with a terminal on standard output and standard error redirected, such as node supervisor.js 2> errors.log, gave children readable colored lines. Their Warn and Error records then reached the parent's JSON standard error as supervisor.childOutput text, without their level, code and fields, and without the documented fields.child entry. Those records now keep their shape and gain fields.child, while standard output still shows readable lines

  • e831234: The supervisor no longer fails with a SupervisorError of reason spawn when 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 the shutdownTimeoutMs deadline, and then handles the exit as usual, in both APIs

  • 1d54b09: The supervisor waitForClose method 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 a SupervisorError of reason spawn. The shutdown completes once the child exits, or at the shutdownTimeoutMs deadline 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 !ping reaches the bot as !ping. Before, say delivered the content unchanged, so a test could ignore input that a live bot answers, or the other way round. Now say delivers 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 through say now sees the bot answer it, as it would on Fluxer. Delivering fixtures.message through emit still passes content unchanged

  • f1aebe9: The coding agent guide's list of APIs that Fluxerly does not have, such as client.login and message methods, now stands on its own without naming another library. Its rules section, which fluxerly agents copies into an application's AGENTS.md, keeps the instruction to use only declared methods. Run the command again to refresh an existing section

  • 6655b27: The AGENTS.md section that fluxerly agents writes now records npx --no fluxerly agents or bunx --no-install fluxerly agents as its refresh command, and pnpm projects keep pnpm exec fluxerly agents. These forms never install a package automatically, because the unscoped fluxerly name 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 twoFactorRequired in a community that requires two-factor authentication for moderators, even without filters. The older nsfw: false channel field restores inheritance on create and edit, and nsfwOverride: false marks a channel as not adult-only. Message search in an age-restricted community can include adult content even when includeNsfw is 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 registerMany or the keyed commands object of runBot, such as { type: "int" }, on that descriptor with the list of valid types, and the other commands keep their inferred values. 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 CacheChange example now tracks cached keys, so replaced entries and cleared kinds stay accurate. The ResourceCacheSettings description now names the one preload, the refill after a shard resumes a stored session, which sharding.refillCaches controls

  • 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 addEventListener throws no longer leaves the runBot SIGINT and SIGTERM listeners installed. The runBot function 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 Default and Reply message 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 !ping ran the !ping command as its creator. The message type is now part of MessageCore, so every client keeps it, even with a messageFields selection that leaves it out. A message without a type, 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 ConnectionError that says so, and the supervisor's count for totalShards: "auto" reports that error as the cause of its shardCount failure. The count previously requested the same full page again until connection.startupTimeoutMs passed and then failed with a ConnectionTimeoutError. The cache refill after restored sessions now stops at such a page and logs lifecycle.cacheRefill at Warn, where it previously requested the same page until the client closed without logging the 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 ConnectionError alongside the interruption and the cleanup defect. The native Cause holds all three, and the default API's SdkDefect lists them as reasons. With Effect 4.0.1, the discovery failure had been dropped

  • 7733b23: The display.name helper accepts a message author directly. Its user parameter now allows an absent displayName, as on received message authors, and falls back to the username

  • 3c685e3: The deploying guide and the agent guide now say which runBot handles SIGINT and SIGTERM by default. The default API does unless processSignals is false, while the native Effect runBot handles them only with processSignals: true, because an Effect launcher such as NodeRuntime.runMain already 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 BotOptions no longer calls a missing or blank token misuse that dies with ConfigurationError. As runBot already behaved and documented, the bot fails with a typed ConfigurationError once the other options are valid

  • 7733b23: Start faster by importing each Effect module the SDK uses from its own effect/<Module> subpath instead of the effect package index, which loads every Effect module. Importing @neontechspace/fluxerly now loads 87 Effect modules instead of 203, and @neontechspace/fluxerly/effect loads 133

  • 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 to import

  • 1afd073: Identify affected generic event subscriptions in safe fallback diagnostics

  • c3bbcf9: Cache kinds with a maxAgeMs expiry 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, from messages to emojis and stickers, 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.send commands 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 4000 instead of 1000, in both entry points. Fluxer ends a session when the client closes with 1000 or 1001, so a reconnect after a missed heartbeat or a restart from a SessionStore had to identify again and lost the events sent in between. A final shutdown without a SessionStore still closes with 1000

  • 7733b23: Event handlers use much less CPU while a backlog waits for a free handler slot

  • 3c685e3: The fluxerly init command 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 appends node_modules and .env only 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 presents init before saving the token by hand.

  • 3c685e3: The TypeScript and Effect starters from fluxerly init now typecheck. Node.js runs a .ts file 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 a check script that runs the compiler and prints the command to run it in its next steps, such as npm run check, pnpm run check or bun run check. The JavaScript starter is unchanged

  • 3a6b15b: Initial release of Fluxerly.js

  • 06be6c0: The hosted links.installation URL now starts at Fluxer's API authorization route, https://api.fluxer.app/v1/oauth2/authorize, which redirects to the hosted installation page. The previous https://fluxer.app/oauth2/authorize URL showed a page-not-found error. Installation links for a discovered instance still open that instance's web app directly

  • 9df8363: Update the WebSocket gateway transport dependency from ws 8.21.3 to 8.22.0

  • 25125b0: Log records, unsafe payload records and describeError output now hide the user information of a URL, such as user:password in https://user:password@host/path, 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 an @ in a path, query or fragment is left as it was

  • c927087: A members.iterateChunks request that would be the client's 13th member request in 11 seconds now fails at once with MemberChunkError reason rateLimit and a retryAfterMs wait, 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 reason timeout. 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 with gateway.send count 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 guildMemberUpdate handler. 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, an allowedMentions switch or an attachment field can no longer pass validation with one value and send another. Other operations read caller arrays and options once too. The messages.deleteMany, guilds.fetchCounts, channels.fetchMemberCounts and presence.setMembers methods 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 SdkDefect holds it as its cause, and several failures arrive as one AggregateError. 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 details.responseField set to order, as message history already did. This covers members.fetchPage, guilds.fetchPage, messages.fetchReactionUsers and auditLogs.fetchPage. Previously the error named the last field read from a valid item, such as 1.deaf, which pointed at the wrong cause

  • 6563bf3: API comments now warn where Fluxer applies one operation as several separate writes. A failed roles.delete can leave the role in place after some members already lost it, and a failed channels.delete can leave a channel that already lost its invites, webhooks, attachments and messages. A failed overwrite change on a category, through channels.edit, channels.setPermissionOverwrite or channels.removePermissionOverwrite, can leave some of its channels with the old overwrites, and a retry may not reach them. The channels.reorder comment 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 rateLimit and the remaining wait in retryAfterMs, and logs ratelimit.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 reason timeout, 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, 503 or 504 could not start before the operation deadline, such as a long Retry-After. Such a read previously waited for the whole deadline and failed with reason timeout

  • 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 as messages.fetch or a GET through rest.request retries it at most twice like any other network error. Previously the operation failed with reason response, as if Fluxer had sent malformed data, logged a misleading rest.responseRejected Warn 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 unknown and 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 runBot reply default. Rejection counters, Debug records and custom onReject callbacks still receive the denial, while guards returning an explicit deny reason retain their reply

    Include 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.send command'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 replayed

    Coalesce 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 401 or 403 request 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 appeared

    Keep 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, describeError and native Effect log causes and annotations. SDK error JSON also masks names in summarized causes, without changing the original error or its cause

    List 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.isRetryable can identify eligible transient failures. Code exchange, refresh and revoke calls with unknown outcomes remain unsafe to repeat, and OAuth requests are not retried automatically

    Preserve 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 retained

    Retain unknown uppercase Fluxer error codes in details.providerCode for HTTP 429 failures, including when the required wait exceeds the operation deadline

    Reject raw REST paths whose reserved /v1 prefix or token-authenticated webhook route names use percent-encoded unreserved characters. Keep route checks case-sensitive and do not decode reserved characters

    Compare 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 Resume

    Make 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 ConfigurationError instead of making an unrealistic test pass

    Keep MESSAGE_REACTION_ADD available under automatic filtering when a messageReactionAddMany handler 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 name

  • 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 runBot entry points mask the normalized token in failure reports before a client exists, including tokens surrounded by whitespace and matching quotes

    Effect runBot retains readable token, logging and reportFailure settings when an option getter throws. A throwing getter no longer discards a configured log sink, token masking or reportFailure: 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 SdkDefect reasons

  • 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.identify coordinator, 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 code lifecycle.startupDeadline states the extended 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 429 responses. 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 logs supervisor.memberRequestsLocal at 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

  • 7733b23: In the default API, a configure callback of supervisor.child.run that throws or rejects makes child.run reject with SdkDefect code application.defect. Its cause is an ApplicationError with source supervisor 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 startupTimeoutMs logs supervisor.startupTimeout at Error before the supervisor shuts down. A child force-terminated after ignoring a stop request logs supervisor.terminated at 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 supervisor.identifyUnacknowledged at 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 supervisor.outputFailed at Warn with the error. A failed message send to a child now keeps its error in the supervisor.spawnFailed record

  • 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 reason protocol instead 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 waits identify.minimumSpacingMs after it

  • 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 SdkDefect reasons 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 above error or a silent events or commands category hid a broken handler, and shutdown passed. A failure repeated within the log deduplication window also appeared in failures() only once, and shutdown then rejected for the summary of the suppressed repeats. Each failure now appears in failures() once and counts for shutdown, while logs() still follows the logging settings

  • 0bca1c9: The idle() and say waits 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