Repository: nyxx-discord/nyxx (dart)

Issue:
Provide caching for "special" entities & gateway initialisation data
**Is your feature request related to a problem? Please describe.**
Currently, if the client wants to fetch itself it must call `users.fetchCurrentUser()`. This is wasteful as it could be called once and then cached, but the client has no way of remembering which user in the cache is the current user.

In fact, `fetchCurrentUser` might not even need to be called at all if the client cached the current user ID received in the `READY` event from the Gateway.

**Describe the solution you'd like**
Keep track of "special" entities and information in the READY payload from the gateway.

**Describe alternatives you've considered**
Users can of course do this themselves, but having in built support for it would be nice - nyxx 5.x.x did this for some entities e.g `INyxxWebsocket.self`.

Pull request:
# Description

Closes #542.

After thinking about it, I've decided that the only missing entity was the current user - so I just added a way to specify that.

The entities are partial so we can potentially move to an ID-based system in the future (and because the entities won't be updated).

## Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing functionality to not work as expected)

# Checklist:

- [x] Ran `dart analyze` or `make analyze` and fixed all issues
- [x] Ran `dart format --set-exit-if-changed -l 160 ./lib` or `make format` and fixed all issues
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works

Patch:
diff --git a/lib/src/client.dart b/lib/src/client.dart
index e3b1e56f8..2e60e0502 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -12,6 +12,7 @@ import 'package:nyxx/src/api_options.dart';
 import 'package:nyxx/src/models/application.dart';
 import 'package:nyxx/src/models/guild/guild.dart';
 import 'package:nyxx/src/models/snowflake.dart';
+import 'package:nyxx/src/models/user/user.dart';
 import 'package:nyxx/src/plugin/plugin.dart';
 import 'package:nyxx/src/utils/flags.dart';
 import 'package:oauth2/oauth2.dart';
@@ -63,11 +64,9 @@ abstract class Nyxx {
     return _doConnect(apiOptions, clientOptions, () async {
       final client = NyxxRest._(apiOptions, clientOptions);
 
-      if (clientOptions.applicationId != null) {
-        return client..application = client.applications[clientOptions.applicationId!];
-      }
-
-      return client..application = await client.applications.fetchCurrentApplication();
+      return client
+        .._application = await client.applications.fetchCurrentApplication()
+        .._user = await client.users.fetchCurrentUser();
     }, clientOptions.plugins);
   }
 
@@ -86,11 +85,9 @@ abstract class Nyxx {
     return _doConnect(apiOptions, clientOptions, () async {
       final client = NyxxOAuth2._(apiOptions, clientOptions);
 
-      if (clientOptions.applicationId != null) {
-        return client..application = client.applications[clientOptions.applicationId!];
-      }
-
-      return client..application = await client.applications.fetchCurrentApplication();
+      return client
+        .._application = await client.applications.fetchCurrentApplication()
+        .._user = await client.users.fetchCurrentUser();
     }, clientOptions.plugins);
   }
 
@@ -116,11 +113,9 @@ abstract class Nyxx {
     return _doConnect(apiOptions, clientOptions, () async {
       final client = NyxxGateway._(apiOptions, clientOptions);
 
-      if (clientOptions.applicationId != null) {
-        client.application = client.applications[clientOptions.applicationId!];
-      } else {
-        client.application = await client.applications.fetchCurrentApplication();
-      }
+      client
+        .._application = await client.applications.fetchCurrentApplication()
+        .._user = await client.users.fetchCurrentUser();
 
       // We can't use client.gateway as it is not initialized yet
       final gatewayManager = GatewayManager(client);
@@ -148,7 +143,12 @@ class NyxxRest with ManagerMixin implements Nyxx {
   late final HttpHandler httpHandler = HttpHandler(this);
 
   /// The application associated with this client.
-  late final PartialApplication application;
+  PartialApplication get application => _application;
+  late final PartialApplication _application;
+
+  /// The user associated with this client.
+  PartialUser get user => _user;
+  late final PartialUser _user;
 
   @override
   Logger get logger => options.logger;
@@ -194,7 +194,16 @@ class NyxxOAuth2 with ManagerMixin implements NyxxRest {
   Logger get logger => options.logger;
 
   @override
-  late final PartialApplication application;
+  PartialApplication get application => _application;
+
+  @override
+  late final PartialApplication _application;
+
+  @override
+  PartialUser get user => _user;
+
+  @override
+  late final PartialUser _user;
 
   NyxxOAuth2._(this.apiOptions, this.options);
 
@@ -227,7 +236,16 @@ class NyxxGateway with ManagerMixin, EventMixin implements NyxxRest {
   late final HttpHandler httpHandler = HttpHandler(this);
 
   @override
-  late final PartialApplication application;
+  PartialApplication get application => _application;
+
+  @override
+  late final PartialApplication _application;
+
+  @override
+  PartialUser get user => _user;
+
+  @override
+  late final PartialUser _user;
 
   /// The [Gateway] used by this client to send and receive Gateway events.
   // Initialized in connectGateway due to a circular dependency
diff --git a/lib/src/client_options.dart b/lib/src/client_options.dart
index 8e7d3428a..f2b0a0a3e 100644
--- a/lib/src/client_options.dart
+++ b/lib/src/client_options.dart
@@ -14,7 +14,6 @@ import 'package:nyxx/src/models/guild/member.dart';
 import 'package:nyxx/src/models/guild/scheduled_event.dart';
 import 'package:nyxx/src/models/message/message.dart';
 import 'package:nyxx/src/models/role.dart';
-import 'package:nyxx/src/models/snowflake.dart';
 import 'package:nyxx/src/models/sticker/global_sticker.dart';
 import 'package:nyxx/src/models/sticker/guild_sticker.dart';
 import 'package:nyxx/src/models/user/user.dart';
@@ -93,9 +92,6 @@ class RestClientOptions extends ClientOptions {
   /// The [CacheConfig] to use for the [GuildApplicationCommandManager.permissionsCache] cache.
   final CacheConfig<CommandPermissions> commandPermissionsConfig;
 
-  /// The ID of the application the client is authenticating for.
-  final Snowflake? applicationId;
-
   /// Create a new [RestClientOptions].
   const RestClientOptions({
     super.plugins,
@@ -118,7 +114,6 @@ class RestClientOptions extends ClientOptions {
     this.globalStickerCacheConfig = const CacheConfig(),
     this.applicationCommandConfig = const CacheConfig(),
     this.commandPermissionsConfig = const CacheConfig(),
-    this.applicationId,
   });
 }
 
@@ -150,6 +145,5 @@ class GatewayClientOptions extends RestClientOptions {
     super.voiceStateConfig,
     super.applicationCommandConfig,
     super.commandPermissionsConfig,
-    super.applicationId,
   });
 }


Test patch:
diff --git a/test/integration/rest_integration_test.dart b/test/integration/rest_integration_test.dart
index 47b3b8601..5963b09e8 100644
--- a/test/integration/rest_integration_test.dart
+++ b/test/integration/rest_integration_test.dart
@@ -45,13 +45,21 @@ void main() {
     // refer to the same variable if we use setUp and tearDown. use the All variants
     // to mitigate this.
     setUpAll(() async {
-      client = await Nyxx.connectRest(testToken!, options: RestClientOptions(applicationId: Snowflake.zero));
+      client = await Nyxx.connectRest(testToken!);
     });
 
     tearDownAll(() async {
       await client.close();
     });
 
+    test('client.user', () async {
+      await expectLater(client.user.get(), completes);
+    });
+
+    test('client.application', () {
+      expect(client.application.id, isNot(Snowflake.zero));
+    });
+
     test('applications', () async {
       await expectLater(client.applications.fetchCurrentApplication(), completes);
     });