mineral 5.1.0 copy "mineral: ^5.1.0" to clipboard
mineral: ^5.1.0 copied to clipboard

Mineral is a Discord framework for designing discord bots in Dart.

5.1.0 #

First release since 5.0.0. It carries the ServerGuild rename, the WebSocket resilience work, and the audit remediation from chantier #458.

Breaking changes — ServerGuild rename #

All framework identifiers that used the word "server" to refer to a Discord guild have been renamed to "guild" for consistency with the Discord API.

Renamed classes and types #

  • ServerGuild
  • ServerMessageGuildMessage
  • ServerSettingsGuildSettings
  • ServerAssetsGuildAssets
  • ServerSubscriptionGuildSubscription
  • ServerChannelGuildChannel
  • ServerTextChannelGuildTextChannel
  • ServerVoiceChannelGuildVoiceChannel
  • ServerAnnouncementChannelGuildAnnouncementChannel
  • ServerCategoryChannelGuildCategoryChannel
  • ServerForumChannelGuildForumChannel
  • ServerStageChannelGuildStageChannel
  • ServerCommandContextGuildCommandContext
  • ServerButtonContextGuildButtonContext
  • ServerModalContextGuildModalContext
  • ServerSelectContextGuildSelectContext
  • ServerBucketGuildBucket
  • ServerEventsGuildEvents
  • ServerBanAddEventGuildBanAddEvent

Renamed fields and parameters #

  • serverIdguildId on all entities, managers, and datastore parts
  • resolveServer(...)resolveGuild(...) (marshaller API)
  • client.events.serverclient.events.guild

Renamed event labels #

  • serverCreateguildCreate
  • serverUpdateguildUpdate
  • serverDeleteguildDelete
  • All Server*Event metadata strings → Guild*Event

Cache key changes #

Cache keys changed from server/... to guild/.... Deploy cache invalidation is required — any persisted cache entries under server/... keys will not be read after upgrading.

Migration #

Search-and-replace ServerGuild (CamelCase) and serverguild (snake_case / camelCase prefix) across your bot source code, excluding HTTP-layer strings such as "internal server error".

Breaking changes — public surface #

Several of these sit on code paths that threw on every invocation, so no consumer had working code to break — but all are public-surface changes and are listed as such.

  • VoiceState.isDiscoverable removed. discoverable is not part of Discord's voice state object; the field never carried data and serialize null-cast on every voice state, so no VoiceState could ever be obtained.
  • Invite.inviterId and Invite.createdAt are now nullable. Vanity invites genuinely have no inviter, and created_at only appears on the Invite Metadata extension. A placeholder id would not have stayed inert: Invite.resolveInviter() turns it into a real GET /users/{id}.
  • Permission.usePublicThreads and Permission.usePrivateThreads removed. They duplicated the bit values of createPublicThreads/createPrivateThreads, which made every permission bitfield round-trip carry into the adjacent bit. Discord itself renamed these; the create* members are the current names.
  • CommandBuilder now declares members. It was an empty marker interface; it now requires name, context, toJson(), reduceHandlers() and declaration. Downstream code that implements it must supply them. This is what removes five duplicated type-switches from the command manager and makes a new builder type a compile-time concern rather than a runtime throw.
  • CommandDefinitionBuilder.context<T>() renamed to configure<T>(). It collided with the interface's context getter. The renamed method had no usages anywhere in the repository and no tests; the context getter is the established convention across the other builders.
  • CommandDeclarationBuilder.reduceHandlers(String) is now no-arg. Every call site passed the receiver's own name.
  • EnvPlaceholder() no longer exposes anything without an explicit allowlist. It previously copied the entire process environment — bot token included — into a public substitution table. Pass EnvPlaceholder(keys: {...}) to opt in; the bot token is excluded unconditionally regardless of the allowlist.

Fixed — paths that were broken on every call #

  • message.delete(), message.pin() and every reaction threw a raw TypeError (204 responses were cast through an untyped empty-map literal).
  • guild.roles.get/create/update and guild.emojis.fetch/get threw type 'Null' is not a subtype of type 'String' — five datastore parts omitted the guild_id injection their siblings perform.
  • Guild.assets.icon, .splash, .banner, .discoverySplash, settings.bitfieldPermission, .afkTimeout, .hasWidgetEnabled, .vanityUrlCode and .maxVideoChannelUsers were silently always null or false — serialize read flat keys that normalize nests.
  • Every voice-state deserialization threw; every GIF and LOTTIE sticker threw StateError (FormatType declared only two of Discord's four values).
  • Emoji roles were read as objects; Discord sends bare snowflake strings, and the ids were mapped to the wrong cache-key namespace.
  • Button clicks in direct messages dispatched nothing at all — not the component handler, not even Event.privateButtonClick. The private handler matched ButtonType against the button's custom_id instead of its type, so the lookup failed for every realistic id and the method returned early. Behind that sat two further defects: the interactive component manager was never called, and the author was read from payload['member'], which Discord omits in a DM. All three are fixed together; fixing only the first would have turned a silent no-op into a null-cast crash.

Fixed — resilience #

  • A reconnect that failed before HELLO left intentionalDisconnect set forever, killing the shard permanently while the process stayed alive and looked healthy. The flag is now cleared in a finally, and a separate shuttingDown flag carries the dispose intent it was overloaded with.
  • Resume connected to the bare resume_gateway_url, dropping ?v= and encoding=. Under encoding=etf this made Discord fall back to JSON frames the ETF decoder silently discarded — permanent deafness. The URL is now built in one place, shared by the initial connect and the resume path.
  • Gateway opcode handlers dropped the futures from reconnect(), resume() and heartbeat(), so a FatalGatewayException reached the root zone and terminated the host process.
  • A malformed or deeply nested ETF frame raised RangeError or StackOverflowError — both Error, which the on Exception guard could not catch — killing the isolate.
  • A consumer's button, modal or select-menu handler that threw killed the bot process. Component dispatch now awaits inside the same crash boundary EventListener already used, with an onComponentError callback.
  • ReadyPacket's guard spanned two awaits, so a second shard's READY issued a duplicate global-command registration; the staggered cache clear could also land after GUILD_CREATE had already hydrated the cache.

Fixed — WebSocket resilience #

  • H1 — WebSocket error handler is now always attached before the stream is listened to, preventing unhandled-stream-error crashes on connection failure.
  • H2 — Malformed or unexpected frame payloads are logged and dropped instead of crashing the shard message loop.
  • H3 — A connect() failure (e.g. WebSocketException, SocketException) now triggers the exponential-backoff reconnect strategy rather than silently hanging.
  • H4resume() and reconnect() are no longer fire-and-forget. A FatalGatewayException thrown when maxReconnectAttempts is exceeded is now caught by a supervised catchError handler and routed to a private _handleFatal helper (cancel heartbeat → disconnect client → invoke onFatalDisconnect). The DisconnectAction.fatal path likewise calls _handleFatal instead of throwing synchronously from a stream callback. Heartbeat Timer callbacks in ShardAuthentication are supervised by the same pattern via _onHeartbeatError. onFatalDisconnect is promoted to the WebsocketOrchestratorContract interface so any implementation (including test doubles) can register the callback.

Fixed — correctness #

  • Unclassified HTTP statuses (304, 409, 413, 501) fell through the retry loop and re-sent the request up to five times — five messages, for a POST — then reported the failure as a rate limit. Statuses are now classified by range.
  • Non-JSON error bodies (Cloudflare HTML pages) raised a bare FormatException that bypassed the retry path entirely and leaked the request queue entry.
  • Slash-command registration ignored every HTTP failure except 400 and bypassed the rate-limit bucket entirely, so a bot in N guilds fired N unthrottled PUTs at startup and silently ignored the resulting 429s.
  • Outbound timestamps were serialized in local time with no UTC offset, so member.exclude() could transmit an instant in the past and silently never apply.
  • Invites were cached under the voice-state key, clobbering the inviter's cached voice state on the gateway path.

Security #

  • The publish workflow no longer executes third-party dependency code in the job holding the pub.dev OIDC token. verify (contents: read) runs analyze/test/dry-run; publish (needs: verify, id-token: write) only publishes. pubspec.lock is now committed and CI resolves with --enforce-lockfile.
  • The rate-limit registry no longer keys an unbounded map by raw interaction and webhook tokens, and evicts buckets whose reset window is long past.
  • package:mineral/api.dart no longer re-exports the whole of env_guard. Seventeen files' worth of generic names (Schema, Rule, Property, Validator, Loader, ...) are no longer part of mineral's public namespace.

Internal #

  • ClientBuilder.build()'s wiring is extracted into a pure composeApp function so the composition root can be tested. build() stays synchronous and its behaviour is unchanged. Both hand-closed construction cycles and packetListener.init() now have tests; deleting either line fails exactly one of them. composeApp and AppComposition are hidden from the public barrel — they exist for testability and expose internal types.

Testing #

The fixtures backing the marshaller tests were rebuilt from the Discord wire format. They had been written to match what each serializer expected, so the suite validated the code against itself — green while the framework was broken. A normalize -> serialize round-trip assertion now guards that contract.


5.0.0 #

Highlights #

Mineral drops its built-in HMR scaffolding and relies on package:hmr ^2.0.0 running externally. The two-isolate model (main + development) is gone — bots now execute in a single process and hot reload preserves both the Discord gateway connection and in-memory state across edits.

Breaking changes #

  • ClientBuilder.setHmrDevPort(SendPort?) removed. Bots no longer receive a SendPort from a parent isolate.
  • ClientBuilder.watch(List<Glob>) removed. Configure watched paths in your app's pubspec.yaml under the hmr.include key instead.
  • main() signature change. User entrypoints become standard void main(List<String> args) (no SendPort? port second argument).
  • HmrRunningStrategy deleted. Kernel always uses DefaultRunningStrategy.
  • ReadyPacketMessage and RuntimeState.readyPacketMessage removed — replay is no longer needed because the WebSocket session survives reloads in process.

Migration #

In your bot:

- import 'dart:isolate';
  import 'package:mineral/api.dart';

- void main(List<String> _, SendPort? port) async {
+ void main(List<String> args) async {
    final client = ClientBuilder()
-       .setHmrDevPort(port)
        .setIntent(Intent.allNonPrivileged)
        .build();
    await client.init();
  }

In your bot's pubspec.yaml:

dev_dependencies:
  hmr: ^2.0.0

# optional, defaults to bin/<package>.dart
hmr:
  entrypoint: bin/main.dart

To run with hot reload:

dart run hmr

4.2.0 #

What's Changed #

New Contributors #

Full Changelog: https://github.com/mineral-dart/core/compare/v3.1.0...v4.0.0

4.1.0-dev.1 #

Major Features #

  • Added a wide range of new Discord events: audit logs, invites, typing, polls, auto-moderation, auto-mod triggers, message reaction remove all.
  • Full implementation of Components V2 + Interactive Components V2.
  • Added support for modals and introduced a major refactor of message & modal components (breaking change).
  • Migrated to the hmr package for hot-reload.
  • Improved voice system: missing exports, resolveServer, and member voice states loaded from cache.
  • Enhanced Command Manager + added regex validation for command names.
  • Added createdAt getters across multiple entities.

Internal Improvements #

  • Migrated the EnvironmentService to env_guard.
  • Reworked attachments handling in interactions.
  • Rerun of the ready event to ensure the bot instance always exists.
  • Added several missing exports.

Important Fixes #

  • Fixed crashes related to audit logs.
  • Fixed missing inline parameter in embed addField.
  • Fixed multiple voice-related issues (null channel ID, missing exports, nullable serverId).
  • Fixed IoC binding issues (Bot binding + GlobalStateManager resolution).

4.0.0-dev.11 #

  • Add Message as return type of .send and .reply methods

4.0.0-dev.10 #

  • Rework member Role properties
  • Enforce Snowflake type parsing
  • Change PermissionOverwrite
  • Add missing thumbnail property on MessageEmbedBuilder
  • Implement InteractiveDialog, InteractiveMenu, InteractiveButton

4.0.0-dev.9 #

  • Complete overhaul of the API data structures
  • Continued implementation of API classes
  • Added rate-limit management
  • Implement audit-log event
  • Implement message reaction events

4.0.0-dev.8 #

  • Fix Member option in commands
  • Fix Role option in commands

4.0.0-dev.7 #

  • Add missing LogLevel enum in exports
  • Fix parent channel as ServerChannel

4.0.0-dev.6 #

  • Enhance architecture
  • Move interfaces to dedicated domain
  • Rename mixins
  • Change Dialog methods builder
  • Implement global states
  • Migrate services passing in constructors to ioc resolver
  • Add a reconnection treatment when the heartbeat is missed 3 times
  • Implement multiple running strategies

4.0.0-dev.5 #

  • Add event parameters
  • Prepare integration with mineral_cli

4.0.0-dev.4 #

  • Add server methods
  • Add server events
  • Fix missing server_id property (see pull request)

4.0.0-dev.3 #

  • Move core into src folder
  • Add api import namespace
  • Add container import namespace
  • Add events import namespace
  • Add services import namespace
  • Add utils import namespace

4.0.0-dev.2 #

What's Changed #

4.0.0-dev.1 #

New Contributors #

3.1.0 #

  • Implement Invites
  • Implement invite packets (create, delete)
  • Implement many select menus (dynamic, user, role, channel, mentionable)
  • Migrate of Discord component to builders (wait next release to allow constructor declaration)
  • Improve main.dart file entrypoint
  • Implement new package concept
  • Fix message delete issue (no message when resolving from message id)

3.0.0 #

  • Implement cli & refactor
  • Implement new features
  • Assign true templates
  • Improve core
  • Add commands git
  • Implement Attachments
  • Channel not initialized
  • Implement http builder & split http service
  • Edit attachments in messages
  • Implement message bulk delete
  • Improve cache
  • Interaction and commands in dm channels
  • Improve users

2.6.2 #

  • Fix bad guild id
  • Remove nullable content of Message

2.6.1 #

  • Remove late keyword & refactor
  • Improve ButtonInteration access with getters

2.6.0 #

  • Remove mixins to public access
  • Add createdAt and updatedAt to Message
  • Add correct message type from fetch()

2.5.0 #

  • Add make:service
  • Add <String>.equals(value)
  • Remove String formatters and implement Recase

2.4.1 #

  • Fix wrong template (make:event)
  • Fix wrong template (make:state)

2.4.0 #

  • Redesign of the order guest
  • Implemented the new CLI from mineral_cli.
  • Removing dependencies using ffi
  • Refactor application
  • Move managers to services

2.3.1 #

  • Fix bad User avatar decoration type
  • Improve category.create() return type
  • Make allow and deny to no required params

2.3.0 #

  • Improve context menu declaration
  • Fix bad state matching

2.2.0 #

  • Add plugins access to the MineralContext

2.1.0 #

  • Migrate environment to dedicated package

2.0.0 Release #

  • Improve accessibility
  • Implement lasted Discord updates
  • Move decorators to fully generics
  • Improve collections key matching (thanks generics)
  • Move framework context to dedicated mixin MineralContext
  • Move ioc accessibility to dedicated mixin Container
  • Standardized packages entrypoints
  • Add executable compile feature
  • More..

1.2.1 #

  • Fix wrong userId key into interactions

1.2.0 #

  • Implement setDefaultReactionEmoji method
  • Implement setTags method
  • Implement setDefaultRateLimit method

1.1.0 #

  • Implement forum channels

1.0.8 1.0.9 #

  • Add missing return

1.0.7 #

  • Implement unban method
  • Improve voice member

1.0.6 #

  • Fix CategoryChannel cast
  • Improve nickname getter, it returns the username if nickname is not defined
  • Implement getOrFail and getOr methods on Environment

1.0.5 #

  • Fix missing examples

1.0.4 #

  • Fix badge url

1.0.3 #

  • Improve dart analyse to get 100%
  • Generate api documentation

1.0.2 #

  • Write documentation examples

1.0.1 #

  • Improve readme shields
  • Improve dart analyse to get 100%
  • Remove unimplemented code

1.0.0 Pre-release #

  • Initialize mineral framework project
21
likes
150
points
546
downloads

Documentation

API reference

Publisher

verified publishermineral-dart.dev

Weekly Downloads

Mineral is a Discord framework for designing discord bots in Dart.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

collection, env_guard, eterl, glob, http, intl, logging, mansion, path, recase, uuid, yaml

More

Packages that depend on mineral