From 3fd9efd18867cdf7452e7d0b3cbc3f33d508e699 Mon Sep 17 00:00:00 2001 From: MatthieuCoder Date: Thu, 5 Jan 2023 01:40:50 +0400 Subject: [PATCH] documentation and internals overhaul --- package.json | 7 +- src/commands/ping.ts | 2 +- src/events/client.ts | 34 ------ src/events/event-emitter.ts | 56 ---------- src/events/transport.ts | 127 ---------------------- src/index.ts | 39 ++++--- src/register.ts | 11 +- src/rest.ts | 11 -- src/sys/events/client.ts | 142 +++++++++++++++++++++++++ src/{ => sys}/events/index.ts | 0 src/sys/events/transport.ts | 175 +++++++++++++++++++++++++++++++ src/{ => sys}/handler/builder.ts | 0 src/{ => sys}/handler/index.ts | 15 +-- tsconfig.json | 3 +- yarn.lock | 65 +++++++++++- 15 files changed, 430 insertions(+), 257 deletions(-) delete mode 100644 src/events/client.ts delete mode 100644 src/events/event-emitter.ts delete mode 100644 src/events/transport.ts delete mode 100644 src/rest.ts create mode 100644 src/sys/events/client.ts rename src/{ => sys}/events/index.ts (100%) create mode 100644 src/sys/events/transport.ts rename src/{ => sys}/handler/builder.ts (100%) rename src/{ => sys}/handler/index.ts (83%) diff --git a/package.json b/package.json index 6b31c00..c10f672 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,20 @@ "license": "Apache-2.0", "dependencies": { "@discordjs/builders": "^1.4.0", + "@discordjs/core": "^0.3.0", "@discordjs/rest": "^1.5.0", - "discord-api-types": "^0.37.25", "events": "^3.3.0", "glob-regex": "^0.3.2", + "js-logger": "^1.6.1", "nats": "^2.10.2", "ping": "^0.4.2", - "source-map-support": "^0.5.21" + "source-map-support": "^0.5.21", + "typed-emitter": "^2.1.0" }, "devDependencies": { "@types/node": "^18.11.18", "@types/ping": "^0.4.1", + "discord-api-types": "^0.37.25", "type-fest": "^3.5.0", "typescript": "^4.9.4" }, diff --git a/src/commands/ping.ts b/src/commands/ping.ts index 9bf6bc0..a39e25a 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -5,7 +5,7 @@ import { ApplicationCommandType, InteractionResponseType, } from "discord-api-types/v10"; -import { CommandBuilder, HandlerFn } from "../handler"; +import { CommandBuilder, HandlerFn } from "../sys/handler"; import { promise } from "ping"; type Messages = { diff --git a/src/events/client.ts b/src/events/client.ts deleted file mode 100644 index 06c7903..0000000 --- a/src/events/client.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { GatewayDispatchPayload } from "discord-api-types/v10"; -import { BaseEventEmitter, HandlerFunction } from "./event-emitter"; -import { Transport, TransportOptions } from "./transport"; -import { CamelCase } from "type-fest"; -import { REST } from "@discordjs/rest"; - -type ExtractEvent = Extract< - O & { t: Exclude> }, - { t: U } ->; - -export type Events = { - [P in GatewayDispatchPayload["t"] as `${CamelCase

}`]: [ - ExtractEvent["d"] - ]; -}; - -export class EventClient extends BaseEventEmitter { - public transport: Transport; - // constructs - constructor(private rest: REST) { - super(); - this.transport = new Transport(this, rest); - } - - public async start(options: TransportOptions) { - await this.transport.start(options); - } - - on(name: K, fn: HandlerFunction): this { - this.transport.subscribe(name); - return super.on(name, fn); - } -} diff --git a/src/events/event-emitter.ts b/src/events/event-emitter.ts deleted file mode 100644 index ad64cd5..0000000 --- a/src/events/event-emitter.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { EventEmitter } from "events"; -import { PascalCase } from "type-fest"; -import { Events } from "."; -import { APIInteractionResponse } from "discord-api-types/v10"; - -export type HandlerFunction = ( - ...args: [...Args, ...[resolve?: (data: APIInteractionResponse) => void]] -) => unknown | Promise; - -export type EventsFunctions = { - [P in keyof Events as P extends string ? `on${PascalCase

}` : never]: ( - fn: HandlerFunction - ) => BaseEventEmitter; -}; - -// Typings for the EventClient -export interface BaseEventEmitter extends EventEmitter { - addListener( - name: K, - fn: HandlerFunction - ): this; - - on(name: K, fn: HandlerFunction): this; - - once(name: K, fn: HandlerFunction): this; - - off(name: K, fn: HandlerFunction): this; - - prependListener( - name: K, - fn: HandlerFunction - ): this; - - prependOnceListener( - name: K, - fn: HandlerFunction - ): this; - - removeAllListeners(eventName: keyof Events | undefined): this; - removeListener(eventName: keyof Events): this; - - emit( - name: T, - respond: (data: APIInteractionResponse) => void, - ...args: Events[T] - ): boolean; - listenerCount(event: keyof Events): number; - listeners(event: T): HandlerFunction[]; - rawListeners: this["listeners"]; -} - -export class BaseEventEmitter extends EventEmitter implements BaseEventEmitter { - constructor() { - super(); - } -} diff --git a/src/events/transport.ts b/src/events/transport.ts deleted file mode 100644 index 882ce41..0000000 --- a/src/events/transport.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { connect, ConnectionOptions, NatsConnection } from "nats"; -import { EventClient, Events } from "."; -import globRegex from "glob-regex"; -import { REST } from "@discordjs/rest"; -import { - APIInteractionResponse, - GatewayDispatchPayload, - GatewayInteractionCreateDispatch, - Routes, -} from "discord-api-types/v10"; -import { CamelCase } from "type-fest"; - -export type TransportOptions = { - additionalEvents?: (keyof Events)[]; - nats?: ConnectionOptions; - queue: string; -}; -export class Transport { - private nats: NatsConnection | null = null; - private subscription: Map = new Map(); - private queue?: string; - private events: Set = new Set(); - - constructor(private emitter: EventClient, private rest: REST) {} - - public async start(options: TransportOptions) { - this.nats = await connect(options?.nats); - this.queue = options.queue; - if (options.additionalEvents) { - options.additionalEvents.forEach((a) => this.events.add(a)); - } - - let initial_events = [...this.events]; - - for (let subscription of initial_events) { - await this.subscribe(subscription); - } - } - - public async subscribe(event: string) { - if (!this.nats) { - console.log("Requesting event " + event); - this.events.add(event); - return; - } - let dashed = event.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase()); - event = `nova.cache.dispatch.${dashed.toUpperCase()}`; - - let isAlreadyPresent = [...this.subscription.keys()].reduce( - (previous, current) => { - if (previous) return previous; - let regex = globRegex(current); - - return regex.test(event); - }, - false - ); - - if (isAlreadyPresent) { - console.warn("nats subscription already covered."); - return; - } - - let regex = globRegex(event); - [...this.subscription.keys()].map((key) => { - if (regex.test(key)) { - let v = this.subscription.get(key); - if (!v) { - return; - } - - console.log(`unsubscribing from ${key}`); - v(); - } - }); - - this._subTask(event, this.queue || "default_queue"); - } - - private async _subTask(event: string, queue: string) { - if (!this.nats) { - throw new Error("nats transporter is not started."); - } - - console.log(`subscribing to ${event}`); - let resolve: Function = () => {}; - let task = new Promise((r) => { - resolve = r; - }); - let sub = this.nats.subscribe(event, { queue: "" }); - - const fn = async () => { - for await (let data of sub) { - let string = Buffer.from(data.data).toString("utf-8"); - let d: GatewayDispatchPayload = JSON.parse(string); - let respond: (repond: APIInteractionResponse) => void | null = null; - const camelCased = d.t.toLowerCase().replace(/_([a-z])/g, function (g) { - return g[1].toUpperCase(); - }) as CamelCase<`${typeof d.t}`>; - - if (camelCased === "integrationCreate") { - let interaction = d.d as GatewayInteractionCreateDispatch["d"]; - respond = (respond: APIInteractionResponse) => { - if (data.reply) { - data.respond(Buffer.from(JSON.stringify(respond), "utf-8")); - } else { - this.rest.post( - Routes.webhook(interaction.channel_id, interaction.token), - { body: respond } - ); - } - }; - console.log("expecting reply."); - } - - this.emitter.emit(camelCased, respond, d.d as any); - } - }; - this.subscription.set(event, resolve); - - await Promise.race([task, fn()]); - - console.log(`finished task for ${event}`); - sub.unsubscribe(); - this.subscription.delete(event); - } -} diff --git a/src/index.ts b/src/index.ts index 62cfdcb..c95e664 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,34 @@ -import { EventClient } from "./events/index"; -import { buildHandler } from "./handler"; +import { Client } from "./sys/events"; +import { buildHandler } from "./sys/handler"; import { commands } from "./commands"; -import { rest } from "./rest"; /** - * We instanciate our nova broken client. + * We instanciate our nova broker client. */ -const emitter = new EventClient(rest); - -// We register our slash command handler. -emitter.on("interactionCreate", buildHandler(commands)); - -// We connect ourselves to the nova nats broker. -emitter - .start({ +const emitter = new Client({ + transport: { additionalEvents: [], nats: { servers: ["localhost:4222"], }, queue: "nova-worker-common", - }) - .catch(console.log); + }, + rest: { + api: "http://localhost:8090/api", + }, +}); + +// We register our slash command handler. +emitter.on("interactionCreate", buildHandler(commands)); + +// Simple message handler +emitter.on("messageCreate", (message) => { + if (message.content === "~ping") { + message.client.channels.createMessage(message.channel_id, { + content: `Bonjour! <@${message.author.id}>`, + }); + } +}); + +// We connect ourselves to the nova nats broker. +emitter.start().catch(console.log); diff --git a/src/register.ts b/src/register.ts index af01e76..92ce36a 100644 --- a/src/register.ts +++ b/src/register.ts @@ -1,7 +1,14 @@ +import { REST } from "@discordjs/rest"; import { commands } from "./commands"; -import { registerCommands } from "./handler"; -import { rest } from "./rest"; +import { registerCommands } from "./sys/handler"; +const rest = new REST({ + version: "10", + headers: { Authorization: "" }, + api: "http://localhost:8090/api", + }).setToken("_"); + + /** * We register the commands with discord */ diff --git a/src/rest.ts b/src/rest.ts deleted file mode 100644 index ab9ff54..0000000 --- a/src/rest.ts +++ /dev/null @@ -1,11 +0,0 @@ -require('source-map-support').install(); -import { REST } from "@discordjs/rest"; - -/** - * Rest client used to communicate with discord - */ -export const rest = new REST({ - version: "10", - headers: { Authorization: "" }, - api: "http://localhost:8090/api", -}).setToken("_"); diff --git a/src/sys/events/client.ts b/src/sys/events/client.ts new file mode 100644 index 0000000..90d7447 --- /dev/null +++ b/src/sys/events/client.ts @@ -0,0 +1,142 @@ +import { + APIApplicationCommandPermissionsConstant, + APIInteractionResponse, + APIInteractionResponseCallbackData, + GatewayDispatchPayload, + GatewayInteractionCreateDispatchData, +} from "discord-api-types/v10"; +import { Transport, TransportOptions } from "./transport"; +import { CamelCase, PascalCase } from "type-fest"; +import { REST, RESTOptions } from "@discordjs/rest"; +import { EventEmitter } from "stream"; +import TypedEmitter from "typed-emitter"; + +import { API } from "@discordjs/core"; + +/** + * Maps an event name (O['t']) and a Union O and extracts alla the union members that have a matching O['t'] + * Example: + * type Variant1 = { t: 'type1', myProperty: 1 }; + * type Variant2 = { t: 'type2', anotherProperty: 2 }; + * type ExampleUnion = Variant1 | Variant2; + * + * let variant1: ExtractVariant; // Type of variant1 is Variant1 + * let variant2: ExtractVariant; // Type of variant2 is Variant2 + * + */ +type ExtractVariant = Extract< + O & { t: Exclude> }, + { t: U } +>; + +/** + * Add intrisics properties to the event, such as `client` and `rest` + */ +export type WithIntrisics = T & { client: Client }; +export type EventName = keyof EventsHandlerArguments; +/** + * Reprends a handler function with one argument + */ +export type HandlerFunction = ( + ...args: Arg +) => unknown | Promise; + +export type EventTypes = { + [P in GatewayDispatchPayload["t"]]: WithIntrisics< + ExtractVariant["d"] + >; +}; + +/** + * Maps all events from GatewayDispatchPayload['t'] (GatewayDispatchEvents) and maps them to a camelcase event name + * Also reteives the type of the event using ExtractEvent + */ +export type EventsHandlerArguments = { + [P in keyof EventTypes as `${CamelCase

}`]: HandlerFunction< + [EventTypes[P]] + >; +} & { + interactionCreate: HandlerFunction< + [ + WithIntrisics, + (interactionCreate: APIInteractionResponseCallbackData) => void + ] + >; +}; + +/** + * Defines all the 'on...' functions on the client + * This is implemented by a Proxy + */ +export type EventsFunctions = { + [P in keyof EventsHandlerArguments as P extends string + ? `on${PascalCase

}` + : never]: (fn: EventsHandlerArguments[P]) => Client; +}; + +/** + * Defines all the methods known to be implemented + */ +interface ClientFunctions + extends EventsFunctions, + TypedEmitter, + API {} + +/** + * The real extended class is an EventEmitter. + */ +const UndefinedClient: { new (): ClientFunctions } = EventEmitter as any; + +/** + * nova.js client + * + * Used to interact with nova, emits events from nova + * Example: + * client.on('messageCreate', (message) => { console.log('Message received', message.content) }); + * client.on('interactionCreate', (message) => { }); + */ +export class Client extends UndefinedClient { + private readonly transport: Transport; + private readonly api: API; + public readonly rest: REST; + + constructor(options: { + rest?: Partial; + transport: TransportOptions; + }) { + super(); + this.rest = new REST(options.rest); + this.transport = new Transport(this, options.transport); + this.api = new API(this.rest); + + // This is safe because this event is emitted by the EventEmitter itself. + this.on("newListener" as any, (event: EventName) => { + this.transport.subscribe(event); + }); + + // Using a proxy to provide the 'on...' functionality + return new Proxy(this, { + get(self, symbol) { + let name = symbol.toString(); + if (name.startsWith("on") && name.length > 2) { + // Get the event name + let eventName = [name[2].toLowerCase(), name.slice(3)].join( + "" + ) as EventName; + return (fn: EventsHandlerArguments[typeof eventName]) => + self.on(eventName, fn); + } + + if (self.api[symbol] && self[symbol]) { + return self.api[symbol]; + } + + return self[symbol]; + }, + }); + } + + public start() { + return this.transport.start(); + } +} diff --git a/src/events/index.ts b/src/sys/events/index.ts similarity index 100% rename from src/events/index.ts rename to src/sys/events/index.ts diff --git a/src/sys/events/transport.ts b/src/sys/events/transport.ts new file mode 100644 index 0000000..28be5cf --- /dev/null +++ b/src/sys/events/transport.ts @@ -0,0 +1,175 @@ +import { connect, ConnectionOptions, NatsConnection, Subscription } from "nats"; +import { + Client, + EventName, + EventTypes, + EventsHandlerArguments, + WithIntrisics, +} from "."; +import globRegex from "glob-regex"; +import { + APIInteraction, + APIInteractionResponse, + APIInteractionResponseCallbackData, + GatewayDispatchPayload, + GatewayInteractionCreateDispatch, + Routes, +} from "discord-api-types/v10"; +import { CamelCase } from "type-fest"; + +/** + * Options for the nats transport layer + */ +export type TransportOptions = { + additionalEvents?: (keyof EventsHandlerArguments)[]; + nats?: ConnectionOptions; + queue: string; +}; + +/** + * Transport implements all the communication to Nova using Nats + */ +export class Transport { + // Nats connection + private nats: NatsConnection | null = null; + // Current subscriptions + private subscriptions: Map = new Map(); + // Current subscribed events + private events: Set = new Set(); + + // Creats a new Transport instance. + constructor( + private emitter: Client, + private config: Partial + ) {} + + /** + * Starts a new nats client. + */ + public async start() { + this.nats = await connect(this.config?.nats); + + for (let eventName of this.events) { + await this.subscribe(eventName); + } + if (this.config.additionalEvents) { + for (let eventName of this.config.additionalEvents) { + await this.subscribe(eventName); + } + } + } + + /** + * Subscribe to a new topic + * @param event Event to subscribe to + * @returns + */ + public async subscribe(event: EventName) { + // If nats is not connected, we simply request to subscribe to it at startup + if (!this.nats) { + console.log("Requesting event " + event); + this.events.add(event); + return; + } + + // Since the event names used by this library are camelCase'd we need to + // re-transform it to the UPPER_CASE used by nova. + let dashed = event.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase()); + // Construct the topic name used by nova. + // This **is going to change** as we implement the caching component. + let topic = `nova.cache.dispatch.${dashed.toUpperCase()}`; + + // To avoid having multiple subscriptions covering this event + // we check if each of our subscriptions covers this scope. + let isAlreadyPresent = [...this.subscriptions.keys()].reduce( + (previous, current) => { + if (previous) return previous; + let regex = globRegex(current); + + return regex.test(topic); + }, + false + ); + + // We abord the subscriptions if it's already covered. + if (isAlreadyPresent) { + console.warn("nats subscription already covered."); + return; + } + + // We remove all the subscriptions that are covered by out current subsciptions. + let regex = globRegex(topic); + [...this.subscriptions.keys()].map((key) => { + if (regex.test(key)) { + let subsciption = this.subscriptions.get(key); + if (!subsciption) { + return; + } + + console.log(`unsubscribing from ${key}`); + subsciption.unsubscribe(); + } + }); + + this._subscriptionTask(topic); + } + + // Task that monitors the subscription + // It also listens for a subscription end. + private async _subscriptionTask(topic: string) { + if (!this.nats) { + throw new Error("nats connection is not started"); + } + + console.log(`subscribing to ${topic}`); + // Create the nats subscription + let subscription = this.nats.subscribe(topic, { queue: this.config.queue }); + this.subscriptions.set(topic, subscription); + // Handle each event in the subscription stream. + for await (let publish of subscription) { + try { + // Decode the payload + let event: GatewayDispatchPayload = JSON.parse( + Buffer.from(publish.data).toString("utf-8") + ); + // Transform the event name to a camclCased name + const camelCasedName = event.t + .toLowerCase() + .replace(/_([a-z])/g, function (g) { + return g[1].toUpperCase(); + }) as CamelCase; + + // Since an interaction need a reponse, + // we need to handle the case where nova is not configured + // with a webhook endpoint, hence we need to use a post request + // against webhook execute endpoint with the interaction data. + if (event.t === "INTERACTION_CREATE") { + let interaction = event.d; + let respond = async (respond: APIInteractionResponseCallbackData) => { + if (publish.reply) { + publish.respond(Buffer.from(JSON.stringify(respond), "utf-8")); + } else { + await this.emitter.interactions.reply( + interaction.id, + interaction.token, + respond + ); + } + }; + // Emit the + this.emitter.emit( + camelCasedName, + { ...event.d, client: this.emitter }, + respond + ); + } else { + // Typescript refuses to infer this, whyyy + this.emitter.emit(camelCasedName, { + ...event.d, + client: this.emitter, + } as any); + } + } catch (e) {} + } + } +} diff --git a/src/handler/builder.ts b/src/sys/handler/builder.ts similarity index 100% rename from src/handler/builder.ts rename to src/sys/handler/builder.ts diff --git a/src/handler/index.ts b/src/sys/handler/index.ts similarity index 83% rename from src/handler/index.ts rename to src/sys/handler/index.ts index 9852d60..c9a5343 100644 --- a/src/handler/index.ts +++ b/src/sys/handler/index.ts @@ -3,6 +3,7 @@ import { APIApplicationCommandInteraction, APIInteraction, APIInteractionResponse, + APIInteractionResponseCallbackData, InteractionType, RESTPostAPIApplicationCommandsJSONBody, RESTPostAPIChatInputApplicationCommandsJSONBody, @@ -17,7 +18,7 @@ export type PromiseLike = T | Promise; */ export type HandlerFn = ( data: APIApplicationCommandInteraction -) => PromiseLike; +) => PromiseLike; export type Command = { json: RESTPostAPIChatInputApplicationCommandsJSONBody; @@ -35,11 +36,11 @@ export const registerCommands = async ( rest: REST, applicationId: string ) => { - for (const command of commands) { - await rest.post(Routes.applicationCommands(applicationId), { - body: command.json as RESTPostAPIApplicationCommandsJSONBody, - }); - } + await rest.post(Routes.applicationCommands(applicationId), { + body: [...commands].map( + (x) => x.json + ) as RESTPostAPIApplicationCommandsJSONBody[], + }); }; /** @@ -55,7 +56,7 @@ export const buildHandler = (commands: Iterable) => { return async ( event: APIInteraction, - reply?: (data: APIInteractionResponse) => void + reply?: (data: APIInteractionResponseCallbackData) => void ) => { console.log("executing: ", event.data); if (event.type === InteractionType.ApplicationCommand) { diff --git a/tsconfig.json b/tsconfig.json index e3d5fd8..7525d19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "target": "ES5", "sourceMap": true, "moduleResolution": "node", - "skipLibCheck": true + "skipLibCheck": true, + "incremental": true }, "exclude": ["node_modules"] diff --git a/yarn.lock b/yarn.lock index c276afd..0efdc27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,16 @@ resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3" integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg== +"@discordjs/core@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/core/-/core-0.3.0.tgz#799ab3ad38dc2c037ac4567a3948e47b93ea2d31" + integrity sha512-yp3H+NnaZ/sg0Z4PIVgfH4Ab88MwkXxnzwG5lNxjYiwp7+8qkYjb726qf3OxZOhFXQFt7gYaAaBevHgmPz9vjA== + dependencies: + "@discordjs/rest" "^1.5.0" + "@discordjs/ws" "^0.6.0" + "@vladfrangu/async_event_emitter" "^2.1.2" + discord-api-types "^0.37.23" + "@discordjs/rest@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.5.0.tgz#dc15474ab98cf6f31291bf61bbc72bcf4f30cea2" @@ -38,6 +48,21 @@ resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a" integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ== +"@discordjs/ws@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-0.6.0.tgz#bd479c65c469552530cc0c54908d6c4722731272" + integrity sha512-CGXmDnskIWyitBejQG07ao6MTkxK5OnFJQLan/cNIoMF61mlbTXpcKGnyrD7dhE7B/q2MEWPKfpuYXtFBog1cg== + dependencies: + "@discordjs/collection" "^1.3.0" + "@discordjs/rest" "^1.5.0" + "@discordjs/util" "^0.1.0" + "@sapphire/async-queue" "^1.5.0" + "@types/ws" "^8.5.3" + "@vladfrangu/async_event_emitter" "^2.1.2" + discord-api-types "^0.37.23" + tslib "^2.4.1" + ws "^8.11.0" + "@sapphire/async-queue@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" @@ -61,7 +86,7 @@ resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== -"@types/node@^18.11.18": +"@types/node@*", "@types/node@^18.11.18": version "18.11.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== @@ -71,6 +96,18 @@ resolved "https://registry.yarnpkg.com/@types/ping/-/ping-0.4.1.tgz#98f9c20be196ca6c3c2639528b7e1827b17e84be" integrity sha512-q/D+xQvoqrHvntvz2A0Pb0ImYwnN3zakluUp8O2qoogGoVMVbdY2K/ulxHcCh9TzYzVoojayHBa9gYQDIZ4v0A== +"@types/ws@^8.5.3": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + +"@vladfrangu/async_event_emitter@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.1.2.tgz#85f04380d474edd1e1e5bd380dee04bc933fe995" + integrity sha512-s/L3xf+BPb8EUOcVeqQ1pUwZNaWGl0br/GCCsdui5DTqn8bpGGu1S2JRTO3cyAytWObieetgTzOPuDN8nwNtGA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -122,6 +159,11 @@ inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +js-logger@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/js-logger/-/js-logger-1.6.1.tgz#8f09671b515e4a6f31dced8fdb8923432e2c60af" + integrity sha512-yTgMCPXVjhmg28CuUH8CKjU+cIKL/G+zTu4Fn4lQxs8mRFH/03QTNvEFngcxfg/gRDiQAOoyCKmMTOm9ayOzXA== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -176,6 +218,13 @@ readable-web-to-node-stream@^3.0.2: dependencies: readable-stream "^3.6.0" +rxjs@^7.5.2: + version "7.8.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + dependencies: + tslib "^2.1.0" + safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -227,7 +276,7 @@ ts-mixer@^6.0.2: resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.2.tgz#3e4e4bb8daffb24435f6980b15204cb5b287e016" integrity sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A== -tslib@^2.4.1: +tslib@^2.1.0, tslib@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== @@ -242,6 +291,13 @@ type-fest@^3.5.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.0.tgz#df7b2ef54ea775163c56d087b33e901ce9d657f7" integrity sha512-bI3zRmZC8K0tUz1HjbIOAGQwR2CoPQG68N5IF7gm0LBl8QSNXzkmaWnkWccCUL5uG9mCsp4sBwC8SBrNSISWew== +typed-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" + integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== + optionalDependencies: + rxjs "^7.5.2" + typescript@^4.9.4: version "4.9.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" @@ -268,3 +324,8 @@ web-streams-polyfill@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +ws@^8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== -- 2.39.5