@neontechspace/fluxerly 1000.0.0-nightly.20261101.49 → 1000.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +984 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,985 @@
|
|
|
1
1
|
# @neontechspace/fluxerly
|
|
2
|
+
|
|
3
|
+
## 1000.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
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
|
|
9
|
+
|
|
10
|
+
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
|
|
11
|
+
|
|
12
|
+
- 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`
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
Inputs that now parse differently, shown as the words each gives (arguments after `!say `):
|
|
16
|
+
|
|
17
|
+
- `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`
|
|
18
|
+
- `a'b c'` and `key='a b'`: Now `a'b` with `c'`, and `key='a` with `b'`. Before, `ab c` and `key=a b`
|
|
19
|
+
- `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
|
|
20
|
+
- `'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
|
|
21
|
+
- `'the dogs' bowl'`: Now `the dogs` and `bowl'`. Before, rejected as an unclosed single quote
|
|
22
|
+
- `'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 `日本語`
|
|
23
|
+
- `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`
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
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`.
|
|
30
|
+
|
|
31
|
+
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`.
|
|
32
|
+
|
|
33
|
+
- 0209093: Public type names no longer carry `Default` or `Native` prefixes, and the per-resource request option types become `RequestOptions` and `AuditedRequestOptions`
|
|
34
|
+
|
|
35
|
+
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
|
|
36
|
+
|
|
37
|
+
Migration:
|
|
38
|
+
|
|
39
|
+
| Entry point | Old name | New name |
|
|
40
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
41
|
+
| Both | `ChannelOperationOptions`, `GuildOperationOptions`, `MessageOperationOptions`, `SendOptions`, `UserOperationOptions`, `CountOperationOptions`, `BotApplicationOperationOptions`, `MessageSearchOptions`, `AttachmentRefreshOptions`, `OAuthOperationOptions`, `InstanceResolveOptions` | `RequestOptions` |
|
|
42
|
+
| Both | `ChannelAuditOperationOptions`, `GuildAuditOperationOptions`, `MessageAuditOperationOptions`, `ModerationOptions`, `WebhookOperationOptions` | `AuditedRequestOptions` |
|
|
43
|
+
| Default | `Default` plus any name in the two rows above | `RequestOptions` or `AuditedRequestOptions` |
|
|
44
|
+
| Default | `Default<Name>`, such as `DefaultCanManageOptions`, `DefaultCollectorOptions`, `DefaultRestRequest`, `DefaultPrefixCommandContext`, `DefaultSupervisor`, `DefaultGuards` or `DefaultCommands` | `<Name>` |
|
|
45
|
+
| Effect | `Native<Name>`, such as `NativePrefixCommandContext`, `NativeSupervisor`, `NativeGuards`, `NativeCommands` or `NativeCommandRequirements` | `<Name>` |
|
|
46
|
+
|
|
47
|
+
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
|
|
48
|
+
|
|
49
|
+
- 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.
|
|
50
|
+
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`.
|
|
51
|
+
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"
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
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.
|
|
55
|
+
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.
|
|
56
|
+
`OperationErrorOptions` and the `MessageError` and `MessageCleanupError` constructors accept `providerCode` and `responseField`, and the `OAuthOperationError` constructor accepts `providerCode`
|
|
57
|
+
|
|
58
|
+
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`
|
|
59
|
+
|
|
60
|
+
A `ConfigurationError` thrown by the default `createClient` or `runBot` points at the caller's line
|
|
61
|
+
|
|
62
|
+
Migration: `ApiValidationErrorDetail.providerCode` is now `ApiValidationCode | (string & {})`, so code that assigns it to an `ApiValidationCode` must narrow it first.
|
|
63
|
+
Operation error message text changed, so match on `code`, `reason` and `apiError` rather than on messages
|
|
64
|
+
|
|
65
|
+
- 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
|
|
66
|
+
|
|
67
|
+
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
|
|
68
|
+
|
|
69
|
+
- 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
|
|
70
|
+
|
|
71
|
+
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
|
|
72
|
+
|
|
73
|
+
- 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
|
|
74
|
+
- 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
|
|
75
|
+
|
|
76
|
+
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
|
|
77
|
+
|
|
78
|
+
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
|
|
79
|
+
|
|
80
|
+
Migration:
|
|
81
|
+
|
|
82
|
+
- Replace `durationSeconds: n` with `durationMs: n * 1000` and `deleteMessageSeconds: n` with `deleteMessagesMs: n * 1000` in ban input
|
|
83
|
+
- Replace `options.deleteMessageSeconds` with `options.deleteMessagesMs` on audit-log entries, and divide by 1000 where seconds are still needed
|
|
84
|
+
|
|
85
|
+
- 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.
|
|
86
|
+
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
|
|
87
|
+
|
|
88
|
+
Migration: Replace `type: "user"`, `"channel"` and `"role"` with `"userChoice"`, `"channelChoice"` and `"roleChoice"` in argument schemas, and in code that reads `CommandArgumentMetadata.type`
|
|
89
|
+
|
|
90
|
+
- 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`
|
|
91
|
+
- 7733b23: Rename client members so each name states what it does and each concept uses one word across both entry points.
|
|
92
|
+
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.
|
|
93
|
+
Subscriptions, collectors and state observers all end with `close()`, and a collector's `result()` returns its final collection.
|
|
94
|
+
`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
|
|
95
|
+
|
|
96
|
+
Migration:
|
|
97
|
+
|
|
98
|
+
- Replace `guilds.deleteMine(guildId)` with `guilds.deleteOwnMessages(guildId, { confirm: true })`, and `messages.deleteMine(channelId)` with `messages.deleteOwnMessages(channelId, { confirm: true })`. The required `confirm` option joins the existing timeout and signal options. The operation IDs change from `guilds.deleteMine` and `deleteMine` to `guilds.deleteOwnMessages` and `deleteOwnMessages`
|
|
99
|
+
- Call `members.ban`, `members.unban` and `members.fetchBans` instead of `guilds.ban`, `guilds.unban` and `guilds.fetchBans`, with the same parameter order. Their operation IDs change from `guilds.ban`, `guilds.unban` and `guilds.fetchBans` to `members.ban`, `members.unban` and `members.fetchBans`
|
|
100
|
+
- Rename `invites.fetchChannel` and `invites.fetchGuild` to `invites.fetchForChannel` and `invites.fetchForGuild`, and `webhooks.fetchChannel` and `webhooks.fetchGuild` to `webhooks.fetchForChannel` and `webhooks.fetchForGuild`. Their operation IDs change to the same new names, so code matching `error.operation` on `invites.fetchChannel`, `invites.fetchGuild`, `webhooks.fetchChannel` or `webhooks.fetchGuild` needs updating
|
|
101
|
+
- Replace `application.fetchCurrent()` with `application.fetch()`. The operation ID changes from `application.fetchCurrent` to `application.fetch`
|
|
102
|
+
- Replace `members.fetchHierarchyCheck(target)` with `members.fetchCanManage(target)`. The operation ID changes from `members.fetchHierarchyCheck` to `members.fetchCanManage`
|
|
103
|
+
- Replace `members.setMute(target, true)` with `members.setMute(target, { muted: true })`, and `members.setDeaf(target, true)` with `members.setDeaf(target, { deafened: true })`. The exported `VoiceMuteInput` and `VoiceDeafenInput` types describe these inputs
|
|
104
|
+
- Call `close()` instead of `subscription.unsubscribe()` on subscriptions, and instead of `collector.stop()` on message and reaction collectors, in both entry points
|
|
105
|
+
- Call a collector's `result()` instead of its `waitForClose()`. The operation IDs change from `collector.waitForClose` and `reactionCollector.waitForClose` to `collector.result` and `reactionCollector.result`
|
|
106
|
+
- The default `client.observeState(listener)` returns a `StateObserver` instead of a function. Call `observer.close()` where the returned function was called
|
|
107
|
+
- A malformed target passed to `client.messages.get` throws `MessageOperationError` with operation `messages.get` instead of `get`, matching `users.get` and `channels.get`, and an `SdkDefect` from that lookup also names `messages.get`. Code that compared a lookup failure's `operation` with `"get"` compares it with `"messages.get"` instead
|
|
108
|
+
- Replace `client.events(event)` with `client.subscribe(event)`, which no longer shares a name with the `runBot` events option. The operation ID changes from `events` to `subscribe`
|
|
109
|
+
- Read `MessageError.outcome` instead of `MessageError.delivery`. Replace `"notSent"` checks with `outcome !== "unknown"`, or distinguish `"notDispatched"` from `"rejected"`, and pass `outcome` instead of `delivery` when constructing the error
|
|
110
|
+
|
|
111
|
+
- 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
|
|
112
|
+
|
|
113
|
+
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
|
|
114
|
+
|
|
115
|
+
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
|
|
116
|
+
|
|
117
|
+
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
|
|
118
|
+
|
|
119
|
+
- 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
|
|
120
|
+
|
|
121
|
+
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 }`
|
|
122
|
+
|
|
123
|
+
- 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
|
|
124
|
+
- 7733b23: Base every SDK error on the exported `FluxerlyError` with a stable `code`, an optional fix `hint`, frozen `details`, a standard `cause` and `toJSON()`.
|
|
125
|
+
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.
|
|
126
|
+
Messages and `toJSON()` output mask credential patterns, including application text quoted by `ApplicationError` and `SdkDefect`, while `cause` keeps the original value.
|
|
127
|
+
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
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
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.
|
|
131
|
+
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.
|
|
132
|
+
Add `ApplicationError` for failures raised by application callbacks such as a `runBot` `setup` or `commands` callback or a `keepTyping` task
|
|
133
|
+
|
|
134
|
+
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.
|
|
135
|
+
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.
|
|
136
|
+
`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`
|
|
137
|
+
|
|
138
|
+
- 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.
|
|
139
|
+
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.
|
|
140
|
+
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
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
Later reports are logged and counted instead, and a failing hook is logged together with the original failure without being retried.
|
|
144
|
+
Shutdown interrupts running hooks without waiting for one that never finishes, and logs the interrupted and queued reports.
|
|
145
|
+
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
|
|
146
|
+
|
|
147
|
+
Migration: `HandlerErrorReport` is replaced by `FailureReport`, and the message cache `onError` option and `CachePolicyErrorReport` are removed in favour of the client's `onError`.
|
|
148
|
+
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`.
|
|
149
|
+
`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`
|
|
150
|
+
|
|
151
|
+
- 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
|
|
152
|
+
|
|
153
|
+
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
|
|
154
|
+
|
|
155
|
+
- 7733b23: The `runBot` function lets running work finish before it stops, in both entry points.
|
|
156
|
+
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
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
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.
|
|
160
|
+
An invalid `drainMs` throws `ConfigurationError` in the default API and dies with it in the Effect API
|
|
161
|
+
|
|
162
|
+
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
|
|
163
|
+
|
|
164
|
+
Migration: To keep cancelling running handlers as soon as the bot is asked to stop, pass `drainMs: 0` to `runBot`
|
|
165
|
+
|
|
166
|
+
- 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
|
|
167
|
+
|
|
168
|
+
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
|
|
169
|
+
|
|
170
|
+
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
|
|
171
|
+
|
|
172
|
+
- 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.
|
|
173
|
+
Set `gateway: { onMalformedDispatch: "terminate" }` to end the shard with a protocol `ConnectionError` instead
|
|
174
|
+
|
|
175
|
+
Add an `overflow` handler option with `stop`, `dropOldest` and `dropNewest` policies. Every dropped event is logged at Warn and counted in `diagnostics().counters.eventsDropped`.
|
|
176
|
+
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.
|
|
177
|
+
An unknown event name fails with `ConfigurationError` whose hint suggests the closest valid name when one is similar
|
|
178
|
+
|
|
179
|
+
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.
|
|
180
|
+
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.
|
|
181
|
+
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
|
|
182
|
+
|
|
183
|
+
- 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
|
|
184
|
+
|
|
185
|
+
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
|
|
186
|
+
|
|
187
|
+
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
|
|
188
|
+
|
|
189
|
+
- 2d263aa: Fluxerly needs Node.js 24.15 or newer, up from 24.11, because that is the oldest Node 24 release npm 12 supports
|
|
190
|
+
- 7733b23: Each helper has one public name, and the role hierarchy helpers join the other pure helpers in a namespace:
|
|
191
|
+
|
|
192
|
+
- The top-level `createPkce` export is removed. Use `oauth.createPkce`, which is unchanged
|
|
193
|
+
- The `hierarchy` namespace replaces `canManageHierarchy`, `compareHierarchy` and `isAboveInHierarchy` with `hierarchy.canManage`, `hierarchy.compare` and `hierarchy.isAbove`. The `HierarchyHelpers` type describes it
|
|
194
|
+
|
|
195
|
+
Migration: Replace `createPkce()` with `oauth.createPkce()`, and each loose hierarchy function with its `hierarchy` method
|
|
196
|
+
|
|
197
|
+
- 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`.
|
|
198
|
+
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.
|
|
199
|
+
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
|
|
200
|
+
|
|
201
|
+
Normalize the configured token: Surrounding whitespace and one pair of matching quotes, which `.env` files often add, are removed.
|
|
202
|
+
A token that starts with a `Bot` or `Bearer` scheme is rejected with `ConfigurationError`, since the SDK adds the `Bot` scheme itself.
|
|
203
|
+
The `auth.rejected` hint suggests checking the configured value before regenerating the token
|
|
204
|
+
|
|
205
|
+
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
|
|
206
|
+
|
|
207
|
+
- 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`
|
|
208
|
+
- 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.
|
|
209
|
+
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()`.
|
|
210
|
+
Invalid input throws the same `HelperError`, `AssetUrlError` or `GuildOperationError` with the same `operation` and `reason` that the Result or Effect previously carried.
|
|
211
|
+
In the native API such a throw inside Effect code becomes a defect rather than a typed failure
|
|
212
|
+
|
|
213
|
+
Add `format.tryParseMention`, `format.tryParseTimestamp`, `format.tryParseCustomEmoji`, `snowflakes.tryParse` and `colors.tryParse` for text received from users.
|
|
214
|
+
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
|
|
215
|
+
|
|
216
|
+
Migration:
|
|
217
|
+
|
|
218
|
+
- In the default API, helpers such as `format.userMention(id)` now return plain values directly, so drop `isOk()`, `value` and `_unsafeUnwrap()` handling from helper calls, and catch `HelperError` only where invalid input is possible
|
|
219
|
+
- In the native API, helpers are no longer Effects, so drop `yield*` and `Effect.runPromise` around calls such as `format.userMention(id)`, `links.installation(id)`, `assets.displayAvatar(user)`, `hierarchy.canManage(input)` and `resolved.links.channel(target)`
|
|
220
|
+
- Parse untrusted text with `format.tryParseMention(text)`, `format.tryParseTimestamp(text)`, `format.tryParseCustomEmoji(text)`, `snowflakes.tryParse(text)` or `colors.tryParse(input)` instead of `parseMention`, `parseTimestamp`, `parseCustomEmoji`, `snowflakes.parse` or `colors.parse`, which now throw
|
|
221
|
+
- Native code that deferred validation by building a helper Effect before its input changed now reads the input when the helper is called
|
|
222
|
+
|
|
223
|
+
- 7733b23: Make public types describe what Fluxer actually sends and what builders accept.
|
|
224
|
+
`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.
|
|
225
|
+
`PresenceUpdate.status` is an `ObservedPresenceStatus`, and an unrecognized status from Fluxer becomes `"unknown"` instead of an arbitrary string.
|
|
226
|
+
`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`.
|
|
227
|
+
The type error from calling `build()` on an empty `MessageBuilder` now names the missing body, through the exported `MissingMessageBody` type.
|
|
228
|
+
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
|
|
229
|
+
|
|
230
|
+
Migration:
|
|
231
|
+
|
|
232
|
+
- Narrow a `GuildChannel` on `type`, for example with `ChannelType.Voice`, before reading `bitrate`, `userLimit` or other fields that belong to one channel type
|
|
233
|
+
- 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
|
|
234
|
+
- Compare `PresenceUpdate.status` against the listed values and handle `"unknown"` instead of Fluxer strings outside that list
|
|
235
|
+
- `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
|
|
236
|
+
- `EmbedBuilder.timestamp(value)` converts a `Date` or number with `toISOString()`. An invalid `Date` or number now throws instead of producing an invalid embed
|
|
237
|
+
|
|
238
|
+
- 7733b23: Public types now use one name per concept, and API rejection details are readable in the reference, in both entry points
|
|
239
|
+
|
|
240
|
+
- `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
|
|
241
|
+
- The deprecated `MessageMention` alias is removed. `MessageUser` is the only name for the partial-user shape in mentions and references
|
|
242
|
+
- `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`
|
|
243
|
+
- 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
|
|
244
|
+
|
|
245
|
+
Migration:
|
|
246
|
+
|
|
247
|
+
- Read `displayName` instead of `globalName` on `MemberSearchHit` and `OAuthIdentity`
|
|
248
|
+
- Replace `MessageMention` with `MessageUser`
|
|
249
|
+
- Use `ApiErrorCode`, `ApiProviderCode` or `ApiValidationCode` to branch on codes. Code that compared `explanation` against literal types compares strings instead
|
|
250
|
+
- Effect code that imported one of these types imports the default-API type from `@neontechspace/fluxerly` under its name without the `Default` prefix, or `RequestOptions` or `AuditedRequestOptions` for the operation option types, or uses the Effect option type of the same operation
|
|
251
|
+
|
|
252
|
+
- 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.
|
|
253
|
+
Local setup returns its value directly: Creating clients, registering handlers, creating command routers and reading caches.
|
|
254
|
+
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.
|
|
255
|
+
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
|
|
256
|
+
|
|
257
|
+
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.
|
|
258
|
+
Callback return types accept `unknown`, and handler, collector and supervisor signals are typed as `AbortSignal`.
|
|
259
|
+
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
|
|
260
|
+
|
|
261
|
+
Migration:
|
|
262
|
+
|
|
263
|
+
- Default `createClient(options)`, `createWebhookClient(options)`, `oauth.create(config)` and `supervisor.create(options)` return the client or supervisor directly. Drop `isErr()`, `value` and `_unsafeUnwrap()` handling and catch `ConfigurationError` only where options come from untrusted input
|
|
264
|
+
- Native `createClient`, `createWebhookClient`, `oauth.create` and `supervisor.create` no longer fail with `ConfigurationError`. Invalid options become a defect, so handlers for that typed failure can be removed
|
|
265
|
+
- Default `client.on(event, handler)` returns the `Subscription`, `client.subscribe(event)` returns the `EventSubscription`, and `messages.collect(...)` and `messages.collectReactions(...)` return the collector directly. A gateway that is not ready or an aborted signal now appears in the collector's `result()` instead of the creation result. Native `on`, `collect` and `collectReactions` no longer fail, and a native `subscribe` stream fails only with `EventOverflowError`
|
|
266
|
+
- Cache lookups such as `client.messages.get(reference)` return the cached value or `undefined` directly, and return `undefined` after shutdown instead of `ClientClosedError`. A malformed ID throws the namespace's operation error with reason `input` in the default API and is a defect in the native API. Native lookups otherwise return an Effect that never fails
|
|
267
|
+
- The default `client.cache.entries(kind)` returns the array directly, and native `cache.entries` never fails
|
|
268
|
+
- The `client.permissions.calculate(input)` method returns the `bigint` directly in both entry points and throws `GuildOperationError` with reason `input` for inconsistent data
|
|
269
|
+
- Command routers return values directly in both entry points. The `commands.create`, `register`, `registerMany`, `registerGroup`, `help` and `commands.memoryCooldowns()` calls throw `ConfigurationError` for invalid definitions, and the default `attach` returns the `Subscription`. Drop `yield*` in front of those calls in native code, where `attach` remains an Effect
|
|
270
|
+
- A default cooldown store's `claim` returns the claim or a Promise of it rather than a Result
|
|
271
|
+
- Callbacks that returned an Err to signal an ignored outcome now report a failure. Return nothing, or handle the Result inside the callback, when the failure is expected
|
|
272
|
+
- The `RegistrationError` and `CollectorRegistrationError` exports are removed, because registration misuse now throws `ConfigurationError` and a registration during shutdown returns a closed handle
|
|
273
|
+
- TypeScript consumers that read handler signals or use `await using` need `AbortSignal` and `AsyncDisposable` from the Node.js types, for example `"types": ["node"]`
|
|
274
|
+
|
|
275
|
+
- 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`.
|
|
276
|
+
The same limit applies to IDs inside the accepted mention forms.
|
|
277
|
+
The value `0` and longer or larger values are now rejected as `Invalid` before the command runs.
|
|
278
|
+
Commands that relied on `0` or out-of-range tokens reaching the handler need a `custom` argument instead
|
|
279
|
+
- 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
|
|
280
|
+
|
|
281
|
+
- 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
|
|
282
|
+
- 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 } })`
|
|
283
|
+
- 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
|
|
284
|
+
|
|
285
|
+
Migration:
|
|
286
|
+
|
|
287
|
+
- 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
|
|
288
|
+
- 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
|
|
289
|
+
- 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`
|
|
290
|
+
|
|
291
|
+
- 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
|
|
292
|
+
|
|
293
|
+
- A `client.on` or `client.subscribe` registration, including a command router's `attach`, returns an already-closed subscription. Its handler never runs, `waitForClose` succeeds and the default `next` returns `Ok(null)`. The native `subscribe` Stream ends without events. Each such registration writes a Warn log record with code `events.registeredAfterShutdown`
|
|
294
|
+
- A `messages.collect` or `messages.collectReactions` registration returns a collector whose `result` fails with `ClientClosedError`, as it does for a collector already running when shutdown starts
|
|
295
|
+
- Invalid arguments are misuse and throw or die with `ConfigurationError`, whatever the client state. Registering middleware with `client.use` on a closing client throws or dies with `ClientClosedError`
|
|
296
|
+
|
|
297
|
+
Migration:
|
|
298
|
+
|
|
299
|
+
- Code that handled `ClientClosedError` from these registrations reads the handle instead. Check `client.state`, or read the collector's `result` for `ClientClosedError`
|
|
300
|
+
|
|
301
|
+
- 7733b23: Configure a whole bot with one `runBot` options object in both entry points.
|
|
302
|
+
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.
|
|
303
|
+
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.
|
|
304
|
+
It then creates the client and registers every event and command synchronously. An aborted `signal` with valid options returns success without creating a client.
|
|
305
|
+
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
|
|
306
|
+
|
|
307
|
+
Commands gain features for common bot needs:
|
|
308
|
+
|
|
309
|
+
- 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)`
|
|
310
|
+
- 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`
|
|
311
|
+
- 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
|
|
312
|
+
- 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
|
|
313
|
+
- 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
|
|
314
|
+
- 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
|
|
315
|
+
- 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
|
|
316
|
+
- 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
|
|
317
|
+
- The bound `reply` accepts a plain string
|
|
318
|
+
- 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
|
|
319
|
+
|
|
320
|
+
Migration:
|
|
321
|
+
|
|
322
|
+
- 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
|
|
323
|
+
- 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
|
|
324
|
+
- `CriticalWorkerStoppedError.workerIndex` counts event subscriptions in configuration order, followed by the command router
|
|
325
|
+
- A cooldown without `store` no longer requires one. Pass `per` instead of building the key from the message when the default key fits
|
|
326
|
+
- Keyed `registerMany` entries may omit `arguments` for unrestricted raw args. TypeScript projects with `exactOptionalPropertyTypes` omit it rather than pass `arguments: undefined`
|
|
327
|
+
- 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
|
|
328
|
+
|
|
329
|
+
- 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.
|
|
330
|
+
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
|
|
331
|
+
|
|
332
|
+
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
|
|
333
|
+
|
|
334
|
+
- 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
|
|
335
|
+
|
|
336
|
+
The missing-token hint now also covers projects created by `fluxerly init`: Set `FLUXER_BOT_TOKEN` in the `.env` file that init wrote
|
|
337
|
+
|
|
338
|
+
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
|
|
339
|
+
|
|
340
|
+
- 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
|
|
341
|
+
- 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
|
|
342
|
+
|
|
343
|
+
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
|
|
344
|
+
|
|
345
|
+
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`
|
|
346
|
+
|
|
347
|
+
- 7733b23: The `runBot` function reports a failed run for the process by default, in both entry points.
|
|
348
|
+
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.
|
|
349
|
+
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
|
|
350
|
+
|
|
351
|
+
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`
|
|
352
|
+
|
|
353
|
+
- 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
|
|
354
|
+
- 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`.
|
|
355
|
+
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.
|
|
356
|
+
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.
|
|
357
|
+
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
|
|
358
|
+
|
|
359
|
+
Migration: Supervised child output was previously discarded. Set `childOutput: "ignore"` to keep discarding it
|
|
360
|
+
|
|
361
|
+
- 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
|
|
362
|
+
|
|
363
|
+
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
|
|
364
|
+
|
|
365
|
+
Migration:
|
|
366
|
+
|
|
367
|
+
- To keep the previous behavior, where one unexpected child exit stops the supervisor, set `restart: false`
|
|
368
|
+
- 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
|
|
369
|
+
|
|
370
|
+
- 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
|
|
371
|
+
|
|
372
|
+
Migration: A test that relied on the 404 for an unmatched message send or edit registers that failure with `rest.respond`
|
|
373
|
+
|
|
374
|
+
- 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`
|
|
375
|
+
|
|
376
|
+
Migration: A test that expects `TestTimeoutError` from a wait without options passes `timeoutMs`
|
|
377
|
+
|
|
378
|
+
- 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
|
|
379
|
+
|
|
380
|
+
`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
|
|
381
|
+
|
|
382
|
+
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
|
|
383
|
+
|
|
384
|
+
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
|
|
385
|
+
|
|
386
|
+
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`
|
|
387
|
+
|
|
388
|
+
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
|
|
389
|
+
|
|
390
|
+
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
|
|
391
|
+
|
|
392
|
+
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
|
|
393
|
+
|
|
394
|
+
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
|
|
395
|
+
|
|
396
|
+
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
|
|
397
|
+
|
|
398
|
+
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
|
|
399
|
+
|
|
400
|
+
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
|
|
401
|
+
|
|
402
|
+
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
|
|
403
|
+
|
|
404
|
+
- 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.
|
|
405
|
+
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.
|
|
406
|
+
`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
|
|
407
|
+
|
|
408
|
+
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
|
|
409
|
+
|
|
410
|
+
- 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`
|
|
411
|
+
|
|
412
|
+
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`
|
|
413
|
+
|
|
414
|
+
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
|
|
415
|
+
|
|
416
|
+
- 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.
|
|
417
|
+
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
|
|
418
|
+
|
|
419
|
+
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.
|
|
420
|
+
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.
|
|
421
|
+
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.
|
|
422
|
+
Repeated Warn and Error records are collapsed with a repeat count, and `diagnostics().counters` counts failures, drops, retries, rate-limit waits, reconnects and resumes.
|
|
423
|
+
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.
|
|
424
|
+
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`.
|
|
425
|
+
The `lifecycle.retry` and `lifecycle.connectionLost` records carry `fields.next`, which is `resume` or `identify` and names the next handshake
|
|
426
|
+
|
|
427
|
+
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
|
|
428
|
+
|
|
429
|
+
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
|
|
430
|
+
|
|
431
|
+
Migration: The `development`, `measurements`, `minimumLevel` and `logger` logging settings, `fromStructuredLogger` and `fromEffectLogger` are removed.
|
|
432
|
+
Use `level` and `categories` for levels, `debug` or `FLUXERLY_DEBUG` for Debug records, and `sink` to forward records to another logger.
|
|
433
|
+
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.
|
|
434
|
+
Clients now print startup, readiness, connection loss and shutdown at Info by default. Set `level: "warn"` to print only problems.
|
|
435
|
+
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
|
|
436
|
+
|
|
437
|
+
- 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`
|
|
438
|
+
|
|
439
|
+
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
|
|
440
|
+
|
|
441
|
+
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
|
|
442
|
+
|
|
443
|
+
### Minor Changes
|
|
444
|
+
|
|
445
|
+
- 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
|
|
446
|
+
|
|
447
|
+
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
|
|
448
|
+
|
|
449
|
+
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
|
|
450
|
+
|
|
451
|
+
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
|
|
452
|
+
|
|
453
|
+
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
|
|
454
|
+
|
|
455
|
+
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
|
|
456
|
+
|
|
457
|
+
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
|
|
458
|
+
|
|
459
|
+
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
|
|
460
|
+
|
|
461
|
+
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
|
|
462
|
+
|
|
463
|
+
- 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
|
|
464
|
+
|
|
465
|
+
`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.
|
|
466
|
+
|
|
467
|
+
- 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`
|
|
468
|
+
|
|
469
|
+
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
|
|
470
|
+
|
|
471
|
+
- 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`
|
|
472
|
+
|
|
473
|
+
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
|
|
474
|
+
|
|
475
|
+
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
|
|
476
|
+
|
|
477
|
+
- 321d8bb: Three additions to the shared `errors` object and the `text` helper, plus two fixes.
|
|
478
|
+
|
|
479
|
+
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.
|
|
480
|
+
|
|
481
|
+
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.
|
|
482
|
+
|
|
483
|
+
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.
|
|
484
|
+
|
|
485
|
+
`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.
|
|
486
|
+
|
|
487
|
+
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.
|
|
488
|
+
|
|
489
|
+
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.
|
|
490
|
+
|
|
491
|
+
- 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
|
|
492
|
+
- b1b9d72: Fields that Fluxer documents on gateway events and the SDK used to drop now reach handlers, in both APIs
|
|
493
|
+
|
|
494
|
+
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`
|
|
495
|
+
|
|
496
|
+
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
|
|
497
|
+
|
|
498
|
+
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
|
|
499
|
+
|
|
500
|
+
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
|
|
501
|
+
|
|
502
|
+
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
|
|
503
|
+
|
|
504
|
+
- 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
|
|
505
|
+
- 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`
|
|
506
|
+
- 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
|
|
507
|
+
|
|
508
|
+
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.
|
|
509
|
+
|
|
510
|
+
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
|
|
511
|
+
|
|
512
|
+
- 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
|
|
513
|
+
- 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
|
|
514
|
+
|
|
515
|
+
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
|
|
516
|
+
|
|
517
|
+
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
|
|
518
|
+
|
|
519
|
+
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
|
|
520
|
+
|
|
521
|
+
- 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
|
|
522
|
+
- 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
|
|
523
|
+
|
|
524
|
+
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
|
|
525
|
+
|
|
526
|
+
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`
|
|
527
|
+
|
|
528
|
+
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`
|
|
529
|
+
|
|
530
|
+
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
|
|
531
|
+
|
|
532
|
+
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
|
|
533
|
+
|
|
534
|
+
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
|
|
535
|
+
|
|
536
|
+
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`
|
|
537
|
+
|
|
538
|
+
`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
|
|
539
|
+
|
|
540
|
+
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
|
|
541
|
+
|
|
542
|
+
`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
|
|
543
|
+
|
|
544
|
+
`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
|
|
545
|
+
|
|
546
|
+
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
|
|
547
|
+
|
|
548
|
+
- 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
|
|
549
|
+
|
|
550
|
+
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`
|
|
551
|
+
|
|
552
|
+
Behaviour changes to know about:
|
|
553
|
+
|
|
554
|
+
- 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
|
|
555
|
+
- 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
|
|
556
|
+
|
|
557
|
+
- 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
|
|
558
|
+
|
|
559
|
+
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
|
|
560
|
+
|
|
561
|
+
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
|
|
562
|
+
|
|
563
|
+
- 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
|
|
564
|
+
|
|
565
|
+
Behaviour changes to know about:
|
|
566
|
+
|
|
567
|
+
- 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
|
|
568
|
+
- 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
|
|
569
|
+
- 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
|
|
570
|
+
- 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
|
|
571
|
+
|
|
572
|
+
- 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
|
|
573
|
+
|
|
574
|
+
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
|
|
575
|
+
|
|
576
|
+
- 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
|
|
577
|
+
- 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
|
|
578
|
+
- 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
|
|
579
|
+
|
|
580
|
+
`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
|
|
581
|
+
|
|
582
|
+
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
|
|
583
|
+
|
|
584
|
+
- 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
|
|
585
|
+
- 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
|
|
586
|
+
|
|
587
|
+
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
|
|
588
|
+
|
|
589
|
+
- 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
|
|
590
|
+
|
|
591
|
+
The `errors.apiCode` helper now names announcement, follow, conversion, publishing and published-message editing rejections, such as `invalidFollowTargetChannel`
|
|
592
|
+
|
|
593
|
+
- 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"`.
|
|
594
|
+
`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`
|
|
595
|
+
- 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
|
|
596
|
+
|
|
597
|
+
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
|
|
598
|
+
|
|
599
|
+
Several configuration errors explain the likely fix:
|
|
600
|
+
|
|
601
|
+
- An event named `ready`, `clientReady`, `onReady` or `connected` points to the connected record, the `setup` option of `runBot` and `client.connect()`
|
|
602
|
+
- An unsupported command argument type suggests the supported type it most likely meant, such as `text` for `string`, and lists every supported type
|
|
603
|
+
- A missing bot token mentions `node --env-file=.env bot.js`, the current folder and a `.env` file saved as `.env.txt`
|
|
604
|
+
- 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
|
|
605
|
+
|
|
606
|
+
- 139f4dc: Add atomic keyed command batches with per-command argument inference, plus command-context reply helpers that bind the incoming message and handler cancellation
|
|
607
|
+
- 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
|
|
608
|
+
- 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`.
|
|
609
|
+
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.
|
|
610
|
+
Keys use decimal IDs, such as `channelId:messageId` for messages and `guildId:userId` for members.
|
|
611
|
+
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
|
|
612
|
+
- 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
|
|
613
|
+
- 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`
|
|
614
|
+
- 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.
|
|
615
|
+
Call participants use `CallVoiceState`, which omits Fluxer's internal voice routing fields.
|
|
616
|
+
A malformed dispatch of these types is skipped and counted like other malformed events, without clearing any cache
|
|
617
|
+
- 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
|
|
618
|
+
- 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.
|
|
619
|
+
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.
|
|
620
|
+
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.
|
|
621
|
+
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
|
|
622
|
+
- 7733b23: Add the `observe` client option to export metrics and traces from either entry point to any monitoring system.
|
|
623
|
+
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.
|
|
624
|
+
Observations never contain tokens, payloads or message content, and route templates replace IDs with placeholders.
|
|
625
|
+
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
|
|
626
|
+
- 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
|
|
627
|
+
- 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.
|
|
628
|
+
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") }`
|
|
629
|
+
- 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
|
|
630
|
+
- 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`.
|
|
631
|
+
`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`
|
|
632
|
+
|
|
633
|
+
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.
|
|
634
|
+
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.
|
|
635
|
+
`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
|
|
636
|
+
|
|
637
|
+
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
|
|
638
|
+
|
|
639
|
+
- 7733b23: Provide a native client as an Effect service with `FluxerClient` from the Effect entry point.
|
|
640
|
+
`FluxerClient.layer(options)` creates the client in the layer's scope and shuts it down when that scope closes.
|
|
641
|
+
`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.
|
|
642
|
+
A missing Config value fails the layer with `ConfigError`, while a blank token or another invalid setting dies with `ConfigurationError`.
|
|
643
|
+
Building either layer does not connect. Register handlers, then call `client.connect()` or `client.run()`
|
|
644
|
+
- 7733b23: Add `FluxerTestClient` to `@neontechspace/fluxerly/effect/testing` for applications that read their client from the `FluxerClient` service.
|
|
645
|
+
`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.
|
|
646
|
+
Closing the layer's Scope shuts the client down and dies with `UnhandledTestFailuresError` for a handler failure the test did not read
|
|
647
|
+
- 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.
|
|
648
|
+
The result is a frozen `ExpressionSourceGuild` with the community's `id`, `name`, `icon` hash and badge `features`.
|
|
649
|
+
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`.
|
|
650
|
+
Badge names added by Fluxer later are kept rather than failing the read, and a response missing any documented field fails with reason `response`
|
|
651
|
+
- 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`
|
|
652
|
+
- 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
|
|
653
|
+
- 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
|
|
654
|
+
- 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
|
|
655
|
+
|
|
656
|
+
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
|
|
657
|
+
|
|
658
|
+
- 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.
|
|
659
|
+
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
|
|
660
|
+
|
|
661
|
+
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
|
|
662
|
+
|
|
663
|
+
- 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
|
|
664
|
+
|
|
665
|
+
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
|
|
666
|
+
|
|
667
|
+
- 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.
|
|
668
|
+
The context names the local shard that received the event and the byte length of the gateway frame that carried it
|
|
669
|
+
|
|
670
|
+
Add `client.use(middleware)` to run event middleware around every later `on` handler invocation, including those of existing subscriptions, command routers and `runBot` handlers.
|
|
671
|
+
Middleware runs in registration order, receives the event name, payload, context and subscription ID, and can skip the handler by not calling `next`.
|
|
672
|
+
Failures of middleware and of the handler inside it are reported like handler failures, so middleware cannot hide a handler failure.
|
|
673
|
+
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
|
|
674
|
+
|
|
675
|
+
- 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.
|
|
676
|
+
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.
|
|
677
|
+
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.
|
|
678
|
+
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
|
|
679
|
+
- 7733b23: Add `gateway.ignoredEvents`, `gateway.flags` and `gateway.presence`, sent in each new session's Identify.
|
|
680
|
+
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.
|
|
681
|
+
Setting `flags: { debounceMessageReactions: true }` turns on Fluxer's `DEBOUNCE_MESSAGE_REACTIONS` flag, merging direct-message reaction runs into `messageReactionAddMany`.
|
|
682
|
+
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.
|
|
683
|
+
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
|
|
684
|
+
- 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
|
|
685
|
+
- 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
|
|
686
|
+
- 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
|
|
687
|
+
- 7733b23: Message operations accept a plain string as the whole message and an `EmbedBuilder` wherever they accept embeds, in both entry points.
|
|
688
|
+
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`.
|
|
689
|
+
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
|
|
690
|
+
- 7733b23: Send text-to-speech messages with `tts: true` on `messages.send`, `messages.reply`, `directMessages.send` and the `runBot` reply helpers.
|
|
691
|
+
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.
|
|
692
|
+
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.
|
|
693
|
+
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
|
|
694
|
+
- 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`.
|
|
695
|
+
`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`
|
|
696
|
+
- 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.
|
|
697
|
+
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
|
|
698
|
+
- 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
|
|
699
|
+
- 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
|
|
700
|
+
|
|
701
|
+
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`
|
|
702
|
+
|
|
703
|
+
- 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`
|
|
704
|
+
- 7733b23: Export the types that public signatures already referenced, so they can be named in application code and appear in the reference.
|
|
705
|
+
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`.
|
|
706
|
+
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`
|
|
707
|
+
|
|
708
|
+
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
|
|
709
|
+
|
|
710
|
+
- 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
|
|
711
|
+
- 7733b23: Add `client.rest.request({ method, path, query?, body?, files?, auditReason?, timeoutMs? })` for Fluxer API routes without an SDK method.
|
|
712
|
+
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.
|
|
713
|
+
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.
|
|
714
|
+
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.
|
|
715
|
+
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.
|
|
716
|
+
The `files` field accepts the message attachment inputs and sends them inline as multipart form data with their metadata in `payload_json`
|
|
717
|
+
- 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).
|
|
718
|
+
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.
|
|
719
|
+
The `defaultTimeoutMs` setting replaces the fixed 30,000 ms deadline of REST operations and attachment downloads that omit `timeoutMs`.
|
|
720
|
+
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
|
|
721
|
+
|
|
722
|
+
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
|
|
723
|
+
|
|
724
|
+
- 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
|
|
725
|
+
|
|
726
|
+
Set `sharding.refillCaches` to `false` to skip the refill
|
|
727
|
+
|
|
728
|
+
- 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
|
|
729
|
+
- 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.
|
|
730
|
+
In the default `createTestBot`, the signal aborts when the test bot's shutdown starts
|
|
731
|
+
|
|
732
|
+
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
|
|
733
|
+
|
|
734
|
+
- 7733b23: Add scaling options for large bots.
|
|
735
|
+
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.
|
|
736
|
+
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.
|
|
737
|
+
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.
|
|
738
|
+
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.
|
|
739
|
+
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.
|
|
740
|
+
`ConnectionRecoveryOptions`, `IdentifyCoordinator`, `SessionStore` and `SessionSnapshot` are new public types
|
|
741
|
+
- 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
|
|
742
|
+
- ada1d09: Add an optional `runBot` runner to both SDK entry points for critical subscriptions, graceful shutdown and opt-in process signal handling
|
|
743
|
+
|
|
744
|
+
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
|
|
745
|
+
|
|
746
|
+
Keep native Effect context and Cause information while the default API returns expected failures as ResultAsync and rejects with `SdkDefect` for defects
|
|
747
|
+
|
|
748
|
+
- 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
|
|
749
|
+
- 7733b23: Add scaling options to the supervisor in both APIs.
|
|
750
|
+
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.
|
|
751
|
+
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.
|
|
752
|
+
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.
|
|
753
|
+
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.
|
|
754
|
+
`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`
|
|
755
|
+
- 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
|
|
756
|
+
- 7733b23: Add `@neontechspace/fluxerly/testing` and `@neontechspace/fluxerly/effect/testing` for application tests that need no network, token or Fluxer account.
|
|
757
|
+
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.
|
|
758
|
+
The `user` and `heartbeatIntervalMs` settings choose the bot account reported in READY and the HELLO heartbeat interval.
|
|
759
|
+
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.
|
|
760
|
+
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.
|
|
761
|
+
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`.
|
|
762
|
+
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
|
|
763
|
+
|
|
764
|
+
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.
|
|
765
|
+
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
|
|
766
|
+
|
|
767
|
+
Tests wait without building their own promises or sleeping.
|
|
768
|
+
The `TestRoute.next()` control returns the next request a registered response answered, in order, including one that arrived before the call.
|
|
769
|
+
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.
|
|
770
|
+
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.
|
|
771
|
+
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
|
|
772
|
+
|
|
773
|
+
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.
|
|
774
|
+
The default `shutdown()` and `await using` disposal reject with `UnhandledTestFailuresError`, which lists the Error records of those failures.
|
|
775
|
+
The native `shutdown()` fails with it, and closing the scope that created the test client dies with it.
|
|
776
|
+
Each failure is reported once, so a later shutdown succeeds.
|
|
777
|
+
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`
|
|
778
|
+
|
|
779
|
+
- 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.
|
|
780
|
+
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.
|
|
781
|
+
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.
|
|
782
|
+
Custom WebSocket factories receive the handshake headers, including the User-Agent, in `WebSocketOptions.headers`
|
|
783
|
+
- 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
|
|
784
|
+
- 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`.
|
|
785
|
+
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
|
|
786
|
+
- 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
|
|
787
|
+
- 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
|
|
788
|
+
- 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
|
|
789
|
+
|
|
790
|
+
### Patch Changes
|
|
791
|
+
|
|
792
|
+
- 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
|
|
793
|
+
- 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
|
|
794
|
+
- 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
|
|
795
|
+
- 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
|
|
796
|
+
- 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
|
|
797
|
+
- 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
|
|
798
|
+
- 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
|
|
799
|
+
- 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
|
|
800
|
+
- 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
|
|
801
|
+
- 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
|
|
802
|
+
- 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
|
|
803
|
+
- 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.
|
|
804
|
+
- 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.
|
|
805
|
+
- 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
|
|
806
|
+
- 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
|
|
807
|
+
|
|
808
|
+
The `MessageCleanupError` from a `preview` whose `filter` threw now keeps the thrown value as `cause`, as documented
|
|
809
|
+
|
|
810
|
+
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
|
|
811
|
+
|
|
812
|
+
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
|
|
813
|
+
|
|
814
|
+
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
|
|
815
|
+
|
|
816
|
+
The `CommandArgumentMetadata` comments no longer give `user`, which registration rejects, as a type example, and say that a custom argument can also use `rest`
|
|
817
|
+
|
|
818
|
+
- 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
|
|
819
|
+
|
|
820
|
+
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"
|
|
821
|
+
|
|
822
|
+
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"
|
|
823
|
+
|
|
824
|
+
- 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
|
|
825
|
+
- 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
|
|
826
|
+
- 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`.
|
|
827
|
+
- 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
|
|
828
|
+
- 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
|
|
829
|
+
|
|
830
|
+
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
|
|
831
|
+
|
|
832
|
+
- 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
|
|
833
|
+
- 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
|
|
834
|
+
- 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
|
|
835
|
+
- 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
|
|
836
|
+
- 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
|
|
837
|
+
- 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
|
|
838
|
+
- 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
|
|
839
|
+
- 1afd073: Reject malformed array entries using their indexed values while preserving documented local bounds
|
|
840
|
+
|
|
841
|
+
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
|
|
842
|
+
|
|
843
|
+
- 1afd073: Expose Fluxer's recorded positive member-ban message-deletion duration through audit-log REST reads and gateway events
|
|
844
|
+
- 1afd073: Honor inherited and non-enumerable audit-log iterator bounds, preserving requested cursors and enforcing page limits through both APIs
|
|
845
|
+
- 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
|
|
846
|
+
- 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
|
|
847
|
+
- 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
|
|
848
|
+
- 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
|
|
849
|
+
- 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
|
|
850
|
+
- 139f4dc: Include standalone JavaScript, TypeScript and native Effect bot starters using the public SDK runner for critical subscriptions and awaited cleanup
|
|
851
|
+
|
|
852
|
+
Clarify ordinary handler Promise ownership and add checked-JavaScript consumer coverage and symptom-based troubleshooting using existing diagnostics
|
|
853
|
+
|
|
854
|
+
- 1afd073: Accept voice-channel bitrate requests through 384,000 bits per second and return the value applied by Fluxer
|
|
855
|
+
- 5f6b6dd: Refresh the package README with matching JavaScript, TypeScript and Effect starter setup, explicit lifecycle ownership and installed Effect version guidance
|
|
856
|
+
- 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
|
|
857
|
+
- 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
|
|
858
|
+
- 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
|
|
859
|
+
- 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
|
|
860
|
+
- 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
|
|
861
|
+
- 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
|
|
862
|
+
- 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
|
|
863
|
+
- 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.
|
|
864
|
+
Importing `@neontechspace/fluxerly` now loads 87 Effect modules instead of 203, and `@neontechspace/fluxerly/effect` loads 133
|
|
865
|
+
- 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
|
|
866
|
+
- 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`
|
|
867
|
+
- 1afd073: Identify affected generic event subscriptions in safe fallback diagnostics
|
|
868
|
+
- 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
|
|
869
|
+
- 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
|
|
870
|
+
- 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
|
|
871
|
+
- 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
|
|
872
|
+
- 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
|
|
873
|
+
- 7733b23: Event handlers use much less CPU while a backlog waits for a free handler slot
|
|
874
|
+
- 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.
|
|
875
|
+
- 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
|
|
876
|
+
- 3a6b15b: Initial release of Fluxerly.js
|
|
877
|
+
- 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
|
|
878
|
+
- 9df8363: Update the WebSocket gateway transport dependency from ws 8.21.3 to 8.22.0
|
|
879
|
+
- 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
|
|
880
|
+
- 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
|
|
881
|
+
- 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
|
|
882
|
+
- 7733b23: Keep a sent message in the message cache when its gateway echo arrives before the HTTP response.
|
|
883
|
+
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.
|
|
884
|
+
Other differences between overlapping observations still remove the cached message
|
|
885
|
+
- 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.
|
|
886
|
+
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.
|
|
887
|
+
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.
|
|
888
|
+
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
|
|
889
|
+
- 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
|
|
890
|
+
- 1afd073: Reject array-valued OAuth operation options before discovery or request dispatch
|
|
891
|
+
- 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
|
|
892
|
+
- 1afd073: Preserve unexpected OAuth construction failures as defects attributed to oauth.create, with configuration details kept private
|
|
893
|
+
- 1afd073: Keep OAuth consent, code exchange and revocation requests aligned with the values that passed local validation
|
|
894
|
+
- 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
|
|
895
|
+
- 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
|
|
896
|
+
- 1afd073: Reject role and channel permission writes above Fluxer's signed 64-bit maximum while preserving unsigned 64-bit response values
|
|
897
|
+
- 1afd073: Prevent stale private-conversation snapshots from surviving overlapping direct-message mutations
|
|
898
|
+
- 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
|
|
899
|
+
- 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
|
|
900
|
+
- 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`
|
|
901
|
+
- 1afd073: Validate bounded resource input arrays from one indexed snapshot before encoding or dispatching requests
|
|
902
|
+
- 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
|
|
903
|
+
- 1afd073: Preserve HTTP rejection status and cleanup defects when releasing an error-response reader fails, instead of reporting an ordinary network error
|
|
904
|
+
- 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
|
|
905
|
+
|
|
906
|
+
Snapshot recognized command and cooldown fields before validation and retain those same values, preserving atomic registration and preventing changing getters from replacing validated policies
|
|
907
|
+
|
|
908
|
+
- 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
|
|
909
|
+
|
|
910
|
+
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
|
|
911
|
+
|
|
912
|
+
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
|
|
913
|
+
|
|
914
|
+
- 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
|
|
915
|
+
|
|
916
|
+
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
|
|
917
|
+
|
|
918
|
+
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
|
|
919
|
+
|
|
920
|
+
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
|
|
921
|
+
|
|
922
|
+
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
|
|
923
|
+
|
|
924
|
+
- 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
|
|
925
|
+
|
|
926
|
+
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
|
|
927
|
+
|
|
928
|
+
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
|
|
929
|
+
|
|
930
|
+
List Debug for handled failure reports in the error and log code catalogue, alongside the existing unhandled failure levels
|
|
931
|
+
|
|
932
|
+
- 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
|
|
933
|
+
|
|
934
|
+
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
|
|
935
|
+
|
|
936
|
+
Retain unknown uppercase Fluxer error codes in `details.providerCode` for HTTP 429 failures, including when the required wait exceeds the operation deadline
|
|
937
|
+
|
|
938
|
+
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
|
|
939
|
+
|
|
940
|
+
Compare pin page ordering and pagination progress at full fractional timestamp precision, with timezone offsets normalized by instant. Equal timestamps still remain equal
|
|
941
|
+
|
|
942
|
+
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
|
|
943
|
+
|
|
944
|
+
- 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
|
|
945
|
+
|
|
946
|
+
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
|
|
947
|
+
|
|
948
|
+
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
|
|
949
|
+
|
|
950
|
+
- 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
|
|
951
|
+
|
|
952
|
+
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
|
|
953
|
+
|
|
954
|
+
Both `runBot` entry points mask the normalized token in failure reports before a client exists, including tokens surrounded by whitespace and matching quotes
|
|
955
|
+
|
|
956
|
+
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`
|
|
957
|
+
|
|
958
|
+
- 6eaecbf: Align request text validation with Fluxer's field-specific normalization and UTF-16 limits without rewriting accepted input
|
|
959
|
+
|
|
960
|
+
Preserve global rate-limit pauses when error bodies are unavailable and learn server rate-limit buckets with bounded, resource-aware tracking
|
|
961
|
+
|
|
962
|
+
Bound complete gateway messages before decoding, including fragmented frames, and reject invalid UTF-8 through the transport
|
|
963
|
+
|
|
964
|
+
Keep attachment reads responsive to cancellation and deadlines when sources repeatedly yield empty chunks
|
|
965
|
+
|
|
966
|
+
Use ordinary JavaScript and TypeScript extensions inside ESM package scopes while retaining the existing public package entry points
|
|
967
|
+
|
|
968
|
+
- 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
|
|
969
|
+
- 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.
|
|
970
|
+
Stack traces with `--enable-source-maps`, debuggers and go-to-source still resolve to the original TypeScript
|
|
971
|
+
- 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
|
|
972
|
+
- 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
|
|
973
|
+
- 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
|
|
974
|
+
- 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
|
|
975
|
+
- 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
|
|
976
|
+
- 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
|
|
977
|
+
- 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
|
|
978
|
+
- 01caebf: Treat runner-initiated client closure as normal when stopping a bot, while preserving run and cleanup failures
|
|
979
|
+
|
|
980
|
+
Preserve combined native failures and default-API `SdkDefect` reasons when an operation and cleanup both fail
|
|
981
|
+
|
|
982
|
+
- 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
|
|
983
|
+
- 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
|
|
984
|
+
- 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
|
|
985
|
+
- 1afd073: Keep webhook create and edit requests aligned with the settings that passed local validation
|