#Extending the DJS client with custom properties in TS

9 messages · Page 1 of 1 (latest)

sweet cosmos

When you type your custom command type, you'd pass client as your ExtendedClient, for example, not the DJS client

Genuinely so sorry if I'm just, blatantly missing the point lol, but won't I still have a problem with everything that discord.js provides me? Say, from client event objects?

eg

client.on('interactionCreate', async (interaction) => {
  //(...)
  command.execute(interaction);
}

typescript types interaction as Interaction<CacheType> because that's what djs types it- and thus, interaction.client will be typed as the djs class, not mine.

I understand that I could just pass my custom client, a la command.execute(interaction, customClient), but that's not the goal - I'm more curious if it's actually possible to maintain the portability provided by not having to pass an extra parameter around, while also being as kosher as possible with TS

unkempt zephyr
sweet cosmos > When you type your custom command type, you'd pass client as your ExtendedClie...

I don't know if there's a better way for this coz I'm learning as I go along, but for it to recognize knowing that my ExtendedClient extends the Client of djs, I casted it. interaction.client as ExtendedClient

The other thing you can do is create your own Event class to accept ExtendedClient as another param for your Events, though as you said, you want it to infer as much of it as possible, but unless you customize these interfaces, TS won't know about any of your custom types.

sweet cosmos

Yeah, that's more/less where I am lol.

My question is less of "how can I maintain type safety?" and more of "while maintaining more/less the exact functionality that I have now, is it possible to maintain type safety without requiring more non-dynamic overhead"

Like, I've managed to functionally create exactly what I want, so that the typings "just work™️" everywhere, as if the djs client class itself had my custom properties- but it's somewhat unsafe, and I feel like there is probably something I haven't managed to find yet within the TS toolbox that would let me stay 'kosher', per se

fallen ice

I've seen people suggest module augmentation where you overwrite Base#client with your ExtendedClient class, but I haven't seen much luck when trying that

I made a function to assert that Base#client is actually my custom class, e.g.:

interaction.client; // Type: Client

function assertExtended(client: Client): asserts client is ExtendedClient {
  return client instanceof ExtendedClient;
}

assertExtended(interaction.client);
interaction.client; // Type: ExtendedClient
sweet cosmos

I posted this before I made the thread, but I'll drop it here again for the sake of it:

declare module "discord.js" {
    interface Client {
      commands: Collection<string, Command>;
      log: FungibleLogger;
      db: Db;

      owner?: User;
      supportServer?: Guild;
      supportChannel?: TextChannel;      
    }

    interface Guild {
      db: GuildDB;
    }
}

This is what I'm currently using - essentially I just make sure to import it before doing anything in my index, and things just ~magically~ work,

But of course, all of those fields are undefined/null until I manually assign them later in my index file.

This code follows the above declare module statement to 'initialize' the fields

Client.prototype.commands = null;
Client.prototype.log = null;
Client.prototype.db = db;

Client.prototype.owner = null;
Client.prototype.supportServer = null;
Client.prototype.supportChannel = null;

Actually in digging this up, I forgot that I do actually manually set the db in the prototype, (because presumably any/all clients in the same app will use the same db?)

Maybe the answer is that I just do all my instantiation here, where I'm defining my custom properties anyways? That would ensure that, once my modified class is imposed upon the world, all of its stuff is valid too because it's done at the same time (within the same file import, at least)

Actually, now that I think about it, isn't a js constructor just syntactic sugar for setting Class.prototype.x anyways? Did I just answer my own question?

unkempt zephyr

I guess it's all just preference from here. I don't like to inject anything custom directly into modules coz I know I'll prolly mess up somewhere hah, so I'd prefer to extend whatever is existing and go from there, and more for my OCD of keeping my custom things organized in such a way I can't mess up other libraries.

From the docs, there's only 3 ways, and that's extending the class, casting, or augmenting the modules which is all here ^

sweet cosmos