You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Preface: this RFC (Request For Comments) is meant to gather feedback and opinions for the feature set and some implementation details for @sapphire/framework! While a lot of the things mentioned in this RFC will be opinionated from my side (like what I’d like to see implemented in the module), not everything might be added in the initial version or some extra things might be added as extras.
What is the Sapphire directory?
Some frameworks, such as Nuxt (.nuxt) and Next.js (.next) define an autogenerated directory, this contains types, metadata, and some other artifacts that greatly enhance the user experience. We can follow their convention and implement .sapphire, but we also may use node_modules/.sapphire instead, which is what Prisma does with node_modules/.prisma/client, which seems more suitable for bridge code, explained later in this proposal.
Since Sapphire Framework is a very strict typed library that aims for type safety to avoid silly mistakes such as typos, we require some of the stores to be augmented into the framework's types, this is remarked for preconditions and arguments, however, for ease of use, commands and listeners are not included, which leads to inconsistent type safety, this is done mostly because manually defining a list of commands (and aliases) can be incredibly bothersome and prone to issues.
An ideal solution to this would be similar to Nuxt v3's auto-imports, which generates a TypeScript Definitions file (.d.ts).
CLI
This utility can be integrated with the official Sapphire CLI, and further enhance it since it won't be limited to a single file.
For simplicity, I believe it may be worth checking whether or not we can bring a part of the CLI inside Framework. And if not, a way to extend the CLI with extra packages, so installing @sapphire/cli-examples gives us sapphire examples. We may need the core built-in because of sapphire generate, which will be required for generating bridge and auto-augmentation code.
Large type presets
Big words! The numbers Mason, what do they mean?
Remember when we said Sapphire v2 would be library agnostic? We're at v3 now, and v4 doesn't look like it'll be. Instead, we're attaching to Discord.js more than ever. This harms our future ability to detach and support raw libraries (@discordjs/ws, @discordjs/rest), other libraries (eris), ecosystems (HTTP), or even runtimes (Deno, CF Workers...). And we're doing this deliberately.
But, what EXACTLY is stopping us from being library agnostic?
Types.
Back in v1, when we were the closest we ever were to library agnosticity, Sapphire was straight incredibly low-level and required a lot of usage gotchas, it wasn't your typical framework. Frameworks are made to simplify things, but as far as v1 went, it felt more like an enterprise library that needed to be extended with one's own micro-framework. This is not what users want to do, and is why v2 is designed the way it was.
How would the Sapphire directory fix this?
Simple, not only it can augment the framework with auto-generated user types, it can also solve the aforementioned issues by automatically injecting compatible code and types as a bridge. Imagine sapphire.config.js, where we define library: 'eris', and it changes the Sapphire bridge to use Eris's types instead of Discord.js's. Pieces such as preconditions and arguments would also be replaced to use Eris-compatible pieces rather than Discord.js-compatible ones, and the bridge's automatic type augmentations simplifies many things even further by ensuring that the user doesn't have to do import '@sapphire/framework/register-eris'; or similar to have the types registered, which also would have the gotcha of requiring the file first before anything else.
Where would the presets be at?
We can make packages such as @sapphire/framework-preset-discord.js that include the Discord.js-compatible pieces, as well as bridge types.
Alternative Sapphire directory
We may use node_modules/.sapphire if the bridge code is injected here (most likely will be), but we can alternatively also use .sapphire at the root directory. This can be configurable too.
Compatibility matrix
Just like we can define library in the sapphire.config.js, we can also define the runtime, accepting 'node' | 'deno' and others (such as 'cloudflare-workers'), but we might also want to add a compatibility matrix so the libraries that can be used per runtime are limited, e.g. library: 'discord.js' may not work with runtime: 'cloudflare-workers'.
Command modes
Sapphire v3 features a very large set of types designed to support both message-based commands and slash commands, however, this bloats IntelliSense and makes components such as preconditions less type-safe since we have to define the handlers as optional rather than abstract. There is AllFlowsPrecondition but it's more of an unsafe workaround, as the methods may be renamed and TypeScript won't warn the user about it, resulting on poorer safety. With the conditional type system (which also works like Rust's #[cfg(feature = "...")] at this point), we can define which methods should be abstract by defining an array (commands: ['messages', 'chat-input', 'context-menu']). This feature would also extend to commands.
Enabling 'messages' in this option will also make message-command-listeners be loaded, without extra code.
Disabling 'chat-input' would make Framework not load the chat-input directory, likewise 'context-menu' with the context-menu one.
Conditional loading
Sapphire v3 defines default error listeners which can be opt-out, however, not all users want to disable all the listeners, just a few.
Configuring this should be with load.optional.listeners.errors: boolean | ('ChatInputCommand' | 'CommandApplicationCommandRegistry')[]..., omitting the Core prefix and Error suffix.
For non-optional, we can define load.arguments, load.listeners, and load.preconditions, both taking either a boolean or an array with the names of the pieces to load.
The config file
Referring to the sapphire.config.js, it may import defineSapphireConfiguration from @sapphire/framework or auto-import it, something that is possible with unjs/jiti used by Nuxt v3 RC10 to auto-import defineNuxtConfig in the configuration file.
Preface: this RFC (Request For Comments) is meant to gather feedback and opinions for the feature set and some implementation details for
@sapphire/framework
! While a lot of the things mentioned in this RFC will be opinionated from my side (like what I’d like to see implemented in the module), not everything might be added in the initial version or some extra things might be added as extras.What is the Sapphire directory?
Some frameworks, such as Nuxt (
.nuxt
) and Next.js (.next
) define an autogenerated directory, this contains types, metadata, and some other artifacts that greatly enhance the user experience. We can follow their convention and implement.sapphire
, but we also may usenode_modules/.sapphire
instead, which is what Prisma does withnode_modules/.prisma/client
, which seems more suitable for bridge code, explained later in this proposal.Since Sapphire Framework is a very strict typed library that aims for type safety to avoid silly mistakes such as typos, we require some of the stores to be augmented into the framework's types, this is remarked for preconditions and arguments, however, for ease of use, commands and listeners are not included, which leads to inconsistent type safety, this is done mostly because manually defining a list of commands (and aliases) can be incredibly bothersome and prone to issues.
An ideal solution to this would be similar to Nuxt v3's auto-imports, which generates a TypeScript Definitions file (
.d.ts
).CLI
This utility can be integrated with the official Sapphire CLI, and further enhance it since it won't be limited to a single file.
For simplicity, I believe it may be worth checking whether or not we can bring a part of the CLI inside Framework. And if not, a way to extend the CLI with extra packages, so installing
@sapphire/cli-examples
gives ussapphire examples
. We may need the core built-in because ofsapphire generate
, which will be required for generating bridge and auto-augmentation code.Large type presets
Big words! The numbers Mason, what do they mean?
Remember when we said Sapphire v2 would be library agnostic? We're at v3 now, and v4 doesn't look like it'll be. Instead, we're attaching to Discord.js more than ever. This harms our future ability to detach and support raw libraries (
@discordjs/ws
,@discordjs/rest
), other libraries (eris
), ecosystems (HTTP), or even runtimes (Deno, CF Workers...). And we're doing this deliberately.But, what EXACTLY is stopping us from being library agnostic?
Types.
Back in v1, when we were the closest we ever were to library agnosticity, Sapphire was straight incredibly low-level and required a lot of usage gotchas, it wasn't your typical framework. Frameworks are made to simplify things, but as far as v1 went, it felt more like an enterprise library that needed to be extended with one's own micro-framework. This is not what users want to do, and is why v2 is designed the way it was.
How would the Sapphire directory fix this?
Simple, not only it can augment the framework with auto-generated user types, it can also solve the aforementioned issues by automatically injecting compatible code and types as a bridge. Imagine
sapphire.config.js
, where we definelibrary: 'eris'
, and it changes the Sapphire bridge to use Eris's types instead of Discord.js's. Pieces such as preconditions and arguments would also be replaced to use Eris-compatible pieces rather than Discord.js-compatible ones, and the bridge's automatic type augmentations simplifies many things even further by ensuring that the user doesn't have to doimport '@sapphire/framework/register-eris';
or similar to have the types registered, which also would have the gotcha of requiring the file first before anything else.Where would the presets be at?
We can make packages such as
@sapphire/framework-preset-discord.js
that include the Discord.js-compatible pieces, as well as bridge types.Alternative Sapphire directory
We may use
node_modules/.sapphire
if the bridge code is injected here (most likely will be), but we can alternatively also use.sapphire
at the root directory. This can be configurable too.Compatibility matrix
Just like we can define
library
in thesapphire.config.js
, we can also define theruntime
, accepting'node' | 'deno'
and others (such as'cloudflare-workers'
), but we might also want to add a compatibility matrix so the libraries that can be used per runtime are limited, e.g.library: 'discord.js'
may not work withruntime: 'cloudflare-workers'
.Command modes
Sapphire v3 features a very large set of types designed to support both message-based commands and slash commands, however, this bloats IntelliSense and makes components such as preconditions less type-safe since we have to define the handlers as optional rather than abstract. There is
AllFlowsPrecondition
but it's more of an unsafe workaround, as the methods may be renamed and TypeScript won't warn the user about it, resulting on poorer safety. With the conditional type system (which also works like Rust's#[cfg(feature = "...")]
at this point), we can define which methods should be abstract by defining an array (commands: ['messages', 'chat-input', 'context-menu']
). This feature would also extend to commands.Enabling
'messages'
in this option will also makemessage-command-listeners
be loaded, without extra code.Disabling
'chat-input'
would make Framework not load the chat-input directory, likewise'context-menu'
with the context-menu one.Conditional loading
Sapphire v3 defines default error listeners which can be opt-out, however, not all users want to disable all the listeners, just a few.
Configuring this should be with
load.optional.listeners.errors: boolean | ('ChatInputCommand' | 'CommandApplicationCommandRegistry')[]
..., omitting theCore
prefix andError
suffix.For non-optional, we can define
load.arguments
,load.listeners
, andload.preconditions
, both taking either a boolean or an array with the names of the pieces to load.The config file
Referring to the
sapphire.config.js
, it may importdefineSapphireConfiguration
from@sapphire/framework
or auto-import it, something that is possible with unjs/jiti used by Nuxt v3 RC10 to auto-importdefineNuxtConfig
in the configuration file.Furthermore, we may also support:
sapphire.config.mts
(ESM TypeScript)sapphire.config.mjs
(ESM JavaScript)sapphire.config.cts
(CJS TypeScript)sapphire.config.cjs
(CJS TypeScript)sapphire.config.ts
sapphire.config.js
sapphire.config.yaml
(should we? Transform plugin?)sapphire.config.json
(should we? Transform plugin?)Defaults
The file's defaults may be the closest as v3's defaults for ease of migration.
The text was updated successfully, but these errors were encountered: