From 261030526153c89703407d6bb1da9e2aac4ca58c Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 7 Jan 2023 16:08:55 -0600 Subject: [PATCH 01/59] feat: experimental plugin changes --- src/handler/events/dispatchers.ts | 29 ++-- src/handler/events/observableHandling.ts | 2 +- .../events/userDefinedEventsHandling.ts | 6 +- src/handler/plugins/args.ts | 104 ++++++++++++++ src/handler/plugins/createPlugin.ts | 43 ++++++ src/handler/plugins/plugin.ts | 131 +++--------------- src/handler/sern.ts | 39 +++--- src/handler/structures/enums.ts | 6 +- src/handler/structures/events.ts | 20 ++- src/index.ts | 2 +- src/types/handler.ts | 3 +- src/types/module.ts | 52 +++---- 12 files changed, 242 insertions(+), 195 deletions(-) create mode 100644 src/handler/plugins/args.ts create mode 100644 src/handler/plugins/createPlugin.ts diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index 6b3e0bf7..7ca20074 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -9,7 +9,7 @@ import type { ChatInputCommandInteraction, Interaction, UserContextMenuCommandInteraction, - MessageContextMenuCommandInteraction, + MessageContextMenuCommandInteraction, ClientEvents, } from 'discord.js'; import { SernError } from '../structures/errors'; import treeSearch from '../utilities/treeSearch'; @@ -47,9 +47,7 @@ export function applicationCommandDispatcher(interaction: Interaction) { return (mod: BothCommand | SlashCommand) => ({ mod, execute: () => mod.execute(ctx, args), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([ctx, args], controller)), - ), + eventPluginRes: arrAsync(mod.onEvent.map(plugs => plugs.execute(ctx, args))), }); } } @@ -62,7 +60,7 @@ export function dispatchAutocomplete(interaction: AutocompleteInteraction) { mod, execute: () => selectedOption.command.execute(interaction), eventPluginRes: arrAsync( - selectedOption.command.onEvent.map(e => e.execute(interaction, controller)), + selectedOption.command.onEvent.map(e => e.execute(interaction)), ), }; } @@ -77,7 +75,7 @@ export function modalCommandDispatcher(interaction: ModalSubmitInteraction) { mod, execute: () => mod.execute(interaction), eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([interaction], controller)), + mod.onEvent.map(plugs => plugs.execute(interaction)), ), }); } @@ -87,7 +85,7 @@ export function buttonCommandDispatcher(interaction: ButtonInteraction) { mod, execute: () => mod.execute(interaction), eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([interaction], controller)), + mod.onEvent.map(plugs => plugs.execute(interaction)), ), }); } @@ -105,7 +103,7 @@ export function selectMenuCommandDispatcher(interaction: MessageComponentInterac mod, execute: () => mod.execute(interaction as never), eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([interaction as never], controller)), + mod.onEvent.map(plugs => plugs.execute(interaction)), ), }); } @@ -115,7 +113,7 @@ export function ctxMenuUserDispatcher(interaction: UserContextMenuCommandInterac mod, execute: () => mod.execute(interaction), eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([interaction], controller)), + mod.onEvent.map(plugs => plugs.execute(interaction)), ), }); } @@ -125,7 +123,7 @@ export function ctxMenuMsgDispatcher(interaction: MessageContextMenuCommandInter mod, execute: () => mod.execute(interaction), eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute([interaction], controller)), + mod.onEvent.map(plugs => plugs.execute(interaction)), ), }); } @@ -142,9 +140,7 @@ export function sernEmitterDispatcher(e: SernEmitter) { reducePlugins( from( arrAsync( - cmd.onEvent.map(plug => - plug.execute([event as Payload], controller), - ), + cmd.onEvent.map(plug => plug.execute(event as Payload)), ), ), ), @@ -167,10 +163,7 @@ export function discordEventDispatcher(e: EventEmitter) { reducePlugins( from( arrAsync( - // god forbid I use any!!! - cmd.onEvent.map(plug => - plug.execute([event as any], controller), - ), + cmd.onEvent.map(plug => plug.execute(...event as ClientEvents[keyof ClientEvents])), ), ), ), @@ -196,7 +189,7 @@ export function externalEventDispatcher(e: (e: ExternalEventCommand) => unknown) reducePlugins( from( arrAsync( - cmd.onEvent.map(plug => plug.execute([event], controller)), + cmd.onEvent.map(plug => plug.execute(event, controller)), ), ), ), diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 53e1fa0f..fe4b866d 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -124,7 +124,7 @@ export function resolvePlugins({ mod: DefinedCommandModule | DefinedEventModule; cmdPluginRes: { execute: Awaitable>; - type: PluginType.Command; + type: PluginType.Init; }[]; }) { if (mod.plugins.length === 0) { diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 7c43e9ae..bdde16e0 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -19,8 +19,6 @@ import { import type { ErrorHandling, Logging } from '../contracts'; import { SernError } from '../structures/errors'; import { handleError } from '../contracts/errorHandling'; -import type { Awaitable } from 'discord.js'; -import type { Result } from 'ts-results-es'; /** * Utility function to process command plugins for all Modules @@ -31,10 +29,10 @@ export function processCommandPlugins< >(payload: { mod: T; absPath: string; -}): { type: PluginType.Command; execute: Awaitable> }[] { +}) { return payload.mod.plugins.map(plug => ({ type: plug.type, - execute: plug.execute(payload as any, controller), + execute: plug.execute(payload), })); } diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts new file mode 100644 index 00000000..74958bb1 --- /dev/null +++ b/src/handler/plugins/args.ts @@ -0,0 +1,104 @@ +import type { CommandType } from '../structures/enums'; +import type { PluginType } from '../structures/enums'; +import type { ClientEvents, Message } from 'discord.js'; +import type { + BothCommand, + ButtonCommand, ChannelSelectCommand, + ContextMenuUser, MentionableSelectCommand, ModalSubmitCommand, + Module, RoleSelectCommand, + SlashCommand, StringSelectCommand, + TextCommand, UserSelectCommand, +} from '../../types/module'; +import type { AnyDefinedModule, Args, Payload, Processed, SlashOptions } from '../../types/handler'; +import type Context from '../structures/context'; +import type { MessageContextMenuCommandInteraction } from 'discord.js'; +import type { ContextMenuMsg } from '../../types/module'; +import type { + ButtonInteraction, + RoleSelectMenuInteraction, + StringSelectMenuInteraction, + UserContextMenuCommandInteraction, +} from 'discord.js'; +import type { + ChannelSelectMenuInteraction, + MentionableSelectMenuInteraction, + ModalSubmitInteraction, + UserSelectMenuInteraction, +} from 'discord.js'; +import { EventType } from '../structures/enums'; +import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from '../structures/events'; + + +type CommandArgsMatrix = { + [CommandType.Text]: { + [PluginType.Control] : /* library coupled */ [Message] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.Slash]: { + [PluginType.Control] : [Context, ['slash', /* library coupled */ SlashOptions]] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.Both]: { + [PluginType.Control] : [Context, ['slash'|'text', /* library coupled */ Args]] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.CtxMsg]: { + [PluginType.Control] : [/* library coupled */MessageContextMenuCommandInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.CtxUser]: { + [PluginType.Control] : [/* library coupled */UserContextMenuCommandInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.Button]: { + [PluginType.Control] : [/* library coupled */ButtonInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.StringSelect]: { + [PluginType.Control] : [/* library coupled */StringSelectMenuInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.RoleSelect]: { + [PluginType.Control] : [/* library coupled */RoleSelectMenuInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.ChannelSelect]: { + [PluginType.Control] : [/* library coupled */ChannelSelectMenuInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.MentionableSelect]: { + [PluginType.Control] : [/* library coupled */MentionableSelectMenuInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.UserSelect]: { + [PluginType.Control] : [/* library coupled */UserSelectMenuInteraction] + [PluginType.Init] : [InitArgs>] + }; + [CommandType.Modal]: { + [PluginType.Control] : [/* library coupled */ModalSubmitInteraction] + [PluginType.Init] : [ModalSubmitCommand] + }; +}; + +type EventArgsMatrix = { + [EventType.Discord] : { + [PluginType.Control] : [/* library coupled */ClientEvents[keyof ClientEvents]] + [PluginType.Init] : [InitArgs>] + }; + [EventType.Sern] : { + [PluginType.Control] : [Payload] + [PluginType.Init] : [[InitArgs>]] + }; + [EventType.External] : { + [PluginType.Control] : [unknown[]] + [PluginType.Init] : [ExternalEventCommand] + } +} + +export interface InitArgs { + mod: T; + absPath: string; +} + +export type CommandArgs = CommandArgsMatrix[I][J] +export type EventArgs = EventArgsMatrix[I][J] \ No newline at end of file diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts new file mode 100644 index 00000000..cf19b6e5 --- /dev/null +++ b/src/handler/plugins/createPlugin.ts @@ -0,0 +1,43 @@ +import { CommandType, EventType, PluginType } from '../structures/enums'; +import type { Plugin, PluginResult } from './plugin'; +import type { CommandArgs, EventArgs } from './args'; + +export function Plugin( + name: string, + type: T, + execute: (...args: any[]) => any +) : Plugin { + return { + name, + type, + execute + }; +} + +export function EventInit( + name: string, + execute: (...args: EventArgs) => PluginResult +) { + return Plugin(name, PluginType.Init, execute); +} + +export function CommandInit( + name: string, + execute: (...args: CommandArgs) => PluginResult +) { + return Plugin(name, PluginType.Init, execute); +} + +export function ControlCommand( + name: string, + execute: (...args: CommandArgs) => PluginResult +) { + return Plugin(name, PluginType.Control, execute); +} + +export function ControlEvent( + name: string, + execute: (...args: EventArgs) => PluginResult +) { + return Plugin(name, PluginType.Control, execute); +} \ No newline at end of file diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 2f675039..7712b8eb 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -11,98 +11,35 @@ * Plugins are reminiscent of middleware in express. */ -import type { AutocompleteInteraction, Awaitable, ClientEvents } from 'discord.js'; +import type { Awaitable } from 'discord.js'; import type { Result, Ok, Err } from 'ts-results-es'; -import type { CommandType, SernEventsMapping } from '../../index'; -import { EventType, PluginType } from '../../index'; +import type { CommandType } from '../structures/enums'; import type { CommandModuleDefs, EventModuleDefs } from '../../types/module'; -import type { - DiscordEventCommand, - ExternalEventCommand, - SernEventCommand, -} from '../structures/events'; + +import type { EventType, PluginType } from '../structures/enums'; +import type { InitArgs } from './args'; +import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +export type PluginResult = Awaitable>; export interface Controller { next: () => Ok; stop: () => Err; } -export interface Plugin { - /** @deprecated will be removed in the next update */ +export interface Plugin { name?: string; /** @deprecated will be removed in the next update */ description?: string; type: PluginType; + execute: (...args: Args) => any } -export interface CommandPlugin - extends Plugin { - type: PluginType.Command; - execute: ( - payload: { - mod: CommandModuleDefs[T] & { name: string; description: string }; - absPath: string; - }, - controller: Controller, - ) => Awaitable>; -} -export interface DiscordEmitterPlugin extends Plugin { - type: PluginType.Command; - execute: ( - payload: { mod: DiscordEventCommand & { name: string }; absPath: string }, - controller: Controller, - ) => Awaitable>; -} - -export interface ExternalEmitterPlugin extends Plugin { - type: PluginType.Command; - execute: ( - payload: { mod: ExternalEventCommand & { name: string }; absPath: string }, - controller: Controller, - ) => Awaitable>; -} - -export interface SernEmitterPlugin extends Plugin { - type: PluginType.Command; - execute: ( - payload: { mod: SernEventCommand & { name: string }; absPath: string }, - controller: Controller, - ) => Awaitable>; -} - -export interface AutocompletePlugin extends Plugin { - type: PluginType.Event; - execute: ( - autocmp: AutocompleteInteraction, - controlller: Controller, - ) => Awaitable>; -} - -export interface EventPlugin - extends Plugin { - type: PluginType.Event; - execute: ( - event: Parameters, - controller: Controller, - ) => Awaitable>; -} - -export interface SernEventPlugin - extends Plugin { - name?: T; - type: PluginType.Event; - execute: (args: SernEventsMapping[T], controller: Controller) => Awaitable>; -} - -export interface ExternalEventPlugin extends Plugin { - type: PluginType.Event; - execute: (args: unknown[], controller: Controller) => Awaitable>; +export interface InitPlugin extends Plugin { + type: PluginType.Init; + execute: (args: InitArgs) => PluginResult } -export interface DiscordEventPlugin - extends Plugin { - name?: T; - type: PluginType.Event; - execute: (args: ClientEvents[T], controller: Controller) => Awaitable>; +export interface ControlPlugin extends Plugin { + type: PluginType.Control; } export type CommandModuleNoPlugins = { @@ -111,37 +48,15 @@ export type CommandModuleNoPlugins = { export type EventModulesNoPlugins = { [T in EventType]: Omit; }; -/** - * Event Module Event Plugins - */ -export type EventModuleEventPluginDefs = { - [EventType.Discord]: DiscordEventPlugin; - [EventType.Sern]: SernEventPlugin; - [EventType.External]: ExternalEventPlugin; -}; - -/** - * Event Module Command Plugins - */ -export type EventModuleCommandPluginDefs = { - [EventType.Discord]: DiscordEmitterPlugin; - [EventType.Sern]: SernEmitterPlugin; - [EventType.External]: ExternalEmitterPlugin; -}; - -export type EventModulePlugin = - | EventModuleEventPluginDefs[T] - | EventModuleCommandPluginDefs[T]; -export type CommandModulePlugin = EventPlugin | CommandPlugin; +export type AnyPlugin = ControlPlugin | InitPlugin; +export type AnyCommandPlugin = ControlPlugin | InitPlugin; +export type AnyEventPlugin = ControlPlugin | InitPlugin; -/** - * User inputs this type. Sern processes behind the scenes for better usage - */ -export type InputCommandModule = { - [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: CommandModulePlugin[] }; -}[CommandType]; - -export type InputEventModule = { - [T in EventType]: EventModulesNoPlugins[T] & { plugins?: EventModulePlugin[] }; +export type InputEvent = { + [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; }[EventType]; + +export type InputCommand = { + [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] }; +}[EventType]; \ No newline at end of file diff --git a/src/handler/sern.ts b/src/handler/sern.ts index f016201e..d739b2d6 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -2,23 +2,14 @@ import type Wrapper from './structures/wrapper'; import { processEvents } from './events/userDefinedEventsHandling'; import { CommandType, EventType, PluginType } from './structures/enums'; import type { + ControlPlugin, + InitPlugin, InputCommand, InputEvent, Plugin, - CommandPlugin, - EventModuleCommandPluginDefs, - EventModuleEventPluginDefs, - EventPlugin, - InputCommandModule, - InputEventModule, } from './plugins/plugin'; import InteractionHandler from './events/interactionHandler'; import ReadyHandler from './events/readyHandler'; import MessageHandler from './events/messageHandler'; -import type { - CommandModule, - CommandModuleDefs, - EventModule, - EventModuleDefs, -} from '../types/module'; +import type { CommandModule, CommandModuleDefs, EventModule, EventModuleDefs } from '../types/module'; import { Container, createContainer } from 'iti'; import type { Dependencies, OptionalDependencies } from '../types/handler'; import { composeRoot, containerSubject, useContainer } from './dependencies/provider'; @@ -66,10 +57,10 @@ export const controller = { * The wrapper function to define command modules for sern * @param mod */ -export function commandModule(mod: InputCommandModule): CommandModule { +export function commandModule(mod: InputCommand): CommandModule { const [onEvent, plugins] = partition( mod.plugins ?? [], - el => (el as Plugin).type === PluginType.Event, + el => (el as Plugin).type === PluginType.Control, ); return { ...mod, @@ -81,10 +72,10 @@ export function commandModule(mod: InputCommandModule): CommandModule { * The wrapper function to define event modules for sern * @param mod */ -export function eventModule(mod: InputEventModule): EventModule { +export function eventModule(mod: InputEvent): EventModule { const [onEvent, plugins] = partition( mod.plugins ?? [], - el => (el as Plugin).type === PluginType.Event, + el => (el as Plugin).type === PluginType.Control, ); return { ...mod, @@ -105,16 +96,22 @@ export function makeDependencies(conf: { return useContainer(); } +/** + * @Experimental + * Will be refactored / changed in future + */ export abstract class CommandExecutable { abstract type: Type; - plugins: CommandPlugin[] = []; - onEvent: EventPlugin[] = []; + plugins: InitPlugin[] = []; + onEvent: ControlPlugin[] = []; abstract execute: CommandModuleDefs[Type]['execute']; } - +/**@Experimental + * Will be refactored in future + */ export abstract class EventExecutable { abstract type: Type; - plugins: EventModuleCommandPluginDefs[Type][] = []; - onEvent: EventModuleEventPluginDefs[Type][] = []; + plugins: InitPlugin[] = []; + onEvent: ControlPlugin[] = []; abstract execute: EventModuleDefs[Type]['execute']; } diff --git a/src/handler/structures/enums.ts b/src/handler/structures/enums.ts index fa18e426..f843f1fb 100644 --- a/src/handler/structures/enums.ts +++ b/src/handler/structures/enums.ts @@ -102,13 +102,13 @@ export enum EventType { */ export enum PluginType { /** - * The PluginType for CommandPlugins + * The PluginType for InitPlugins */ - Command = 1, + Init = 1, /** * The PluginType for EventPlugins */ - Event = 2, + Control = 2, } /** * @enum { string } diff --git a/src/handler/structures/events.ts b/src/handler/structures/events.ts index 013957df..c20ac4a2 100644 --- a/src/handler/structures/events.ts +++ b/src/handler/structures/events.ts @@ -1,11 +1,7 @@ import type { SernEventsMapping } from '../../types/handler'; import type { - DiscordEmitterPlugin, - DiscordEventPlugin, - ExternalEmitterPlugin, - ExternalEventPlugin, - SernEmitterPlugin, - SernEventPlugin, + ControlPlugin, + InitPlugin, } from '../plugins/plugin'; import type { Awaitable, ClientEvents } from 'discord.js'; import type { EventType } from './enums'; @@ -15,8 +11,8 @@ export interface SernEventCommand; } @@ -24,8 +20,8 @@ export interface DiscordEventCommand; } @@ -33,7 +29,7 @@ export interface ExternalEventCommand extends Module { name?: string; emitter: string; type: EventType.External; - onEvent: ExternalEventPlugin[]; - plugins: ExternalEmitterPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute(...args: unknown[]): Awaitable; } diff --git a/src/index.ts b/src/index.ts index 8a5edec8..9e450f7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import SernEmitter from './handler/sernEmitter'; -export { eventModule, commandModule, EventExecutable, CommandExecutable } from './handler/sern'; +export { eventModule, commandModule, EventExecutable, CommandExecutable, controller } from './handler/sern'; export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; diff --git a/src/types/handler.ts b/src/types/handler.ts index f1fb9246..6c1e0682 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -2,7 +2,7 @@ import type { CommandInteractionOptionResolver } from 'discord.js'; import type { PayloadType } from '../handler/structures/enums'; import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { EventEmitter } from 'events'; -import type { CommandModule, EventModule, AnyModule } from './module'; +import type { CommandModule, EventModule, AnyModule, Module } from './module'; import type { UnpackFunction } from 'iti'; import type { ErrorHandling, Logging, ModuleManager } from '../handler/contracts'; import type { ModuleStore } from '../handler/structures/moduleStore'; @@ -62,3 +62,4 @@ export type MapDeps = T : [never]; //Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest export type OptionalDependencies = '@sern/logger'; +export type Processed = T & { name: string; description: string }; \ No newline at end of file diff --git a/src/types/module.ts b/src/types/module.ts index 00c742dd..22708c15 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -28,7 +28,7 @@ import type { import { CommandType } from '../handler/structures/enums'; import type { Args, SlashOptions } from './handler'; import type Context from '../handler/structures/context'; -import type { AutocompletePlugin, CommandPlugin, EventPlugin } from '../handler/plugins/plugin'; +import type { InitPlugin, ControlPlugin } from '../handler/plugins/plugin'; import { EventType } from '../handler/structures/enums'; import type { UserSelectMenuInteraction } from 'discord.js'; @@ -41,24 +41,24 @@ export interface Module { export interface TextCommand extends Module { type: CommandType.Text; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; alias?: string[]; execute: (ctx: Context, args: ['text', string[]]) => Awaitable; } export interface SlashCommand extends Module { type: CommandType.Slash; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; options?: SernOptionsData[]; execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; } export interface BothCommand extends Module { type: CommandType.Both; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; alias?: string[]; options?: SernOptionsData[]; execute: (ctx: Context, args: Args) => Awaitable; @@ -66,64 +66,64 @@ export interface BothCommand extends Module { export interface ContextMenuUser extends Module { type: CommandType.CtxUser; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: UserContextMenuCommandInteraction) => Awaitable; } export interface ContextMenuMsg extends Module { type: CommandType.CtxMsg; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: MessageContextMenuCommandInteraction) => Awaitable; } export interface ButtonCommand extends Module { type: CommandType.Button; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: ButtonInteraction) => Awaitable; } export interface StringSelectCommand extends Module { type: CommandType.StringSelect; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: StringSelectMenuInteraction) => Awaitable; } export interface ChannelSelectCommand extends Module { type: CommandType.ChannelSelect; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: ChannelSelectMenuInteraction) => Awaitable; } export interface RoleSelectCommand extends Module { type: CommandType.RoleSelect; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: RoleSelectMenuInteraction) => Awaitable; } export interface MentionableSelectCommand extends Module { type: CommandType.MentionableSelect; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: MentionableSelectMenuInteraction) => Awaitable; } export interface UserSelectCommand extends Module { type: CommandType.UserSelect; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: UserSelectMenuInteraction) => Awaitable; } export interface ModalSubmitCommand extends Module { type: CommandType.Modal; - onEvent: EventPlugin[]; - plugins: CommandPlugin[]; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; execute: (ctx: ModalSubmitInteraction) => Awaitable; } @@ -131,7 +131,7 @@ export interface AutocompleteCommand extends Module { name?: never; description?: never; type?: never; - onEvent: AutocompletePlugin[]; + onEvent: ControlPlugin[]; execute: (ctx: AutocompleteInteraction) => Awaitable; } From e56d38a055341c66a04740b36aec422d1c86c66a Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 7 Jan 2023 20:28:08 -0600 Subject: [PATCH 02/59] more refactors and name changes --- src/handler/plugins/createPlugin.ts | 28 +++++++++++----------------- src/handler/plugins/index.ts | 3 +++ src/handler/plugins/plugin.ts | 24 ++++++++---------------- src/index.ts | 2 +- src/types/module.ts | 2 +- 5 files changed, 24 insertions(+), 35 deletions(-) create mode 100644 src/handler/plugins/index.ts diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index cf19b6e5..b73b8c4e 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -2,42 +2,36 @@ import { CommandType, EventType, PluginType } from '../structures/enums'; import type { Plugin, PluginResult } from './plugin'; import type { CommandArgs, EventArgs } from './args'; -export function Plugin( - name: string, +export function makePlugin( type: T, execute: (...args: any[]) => any -) : Plugin { +): Plugin { return { - name, type, execute }; } -export function EventInit( - name: string, +export function EventInitPlugin( execute: (...args: EventArgs) => PluginResult ) { - return Plugin(name, PluginType.Init, execute); + return makePlugin(PluginType.Init, execute); } -export function CommandInit( - name: string, +export function CommandInitPlugin( execute: (...args: CommandArgs) => PluginResult ) { - return Plugin(name, PluginType.Init, execute); + return makePlugin(PluginType.Init, execute); } -export function ControlCommand( - name: string, +export function CommandControlPlugin( execute: (...args: CommandArgs) => PluginResult -) { - return Plugin(name, PluginType.Control, execute); +) { + return makePlugin(PluginType.Control, execute); } -export function ControlEvent( - name: string, +export function EventControlPlugin( execute: (...args: EventArgs) => PluginResult ) { - return Plugin(name, PluginType.Control, execute); + return makePlugin(PluginType.Control, execute); } \ No newline at end of file diff --git a/src/handler/plugins/index.ts b/src/handler/plugins/index.ts new file mode 100644 index 00000000..6ede51b4 --- /dev/null +++ b/src/handler/plugins/index.ts @@ -0,0 +1,3 @@ +export { EventArgs, InitArgs, CommandArgs } from './args'; +export * from './plugin'; +export * from './createPlugin'; \ No newline at end of file diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 7712b8eb..eae94ac4 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -12,34 +12,27 @@ */ import type { Awaitable } from 'discord.js'; -import type { Result, Ok, Err } from 'ts-results-es'; +import type { Result} from 'ts-results-es'; import type { CommandType } from '../structures/enums'; import type { CommandModuleDefs, EventModuleDefs } from '../../types/module'; import type { EventType, PluginType } from '../structures/enums'; -import type { InitArgs } from './args'; -import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; export type PluginResult = Awaitable>; -export interface Controller { - next: () => Ok; - stop: () => Err; -} export interface Plugin { - name?: string; - /** @deprecated will be removed in the next update */ - description?: string; type: PluginType; execute: (...args: Args) => any } -export interface InitPlugin extends Plugin { +export interface InitPlugin extends Plugin { type: PluginType.Init; - execute: (args: InitArgs) => PluginResult + execute: (...args: Args) => PluginResult } -export interface ControlPlugin extends Plugin { +export interface ControlPlugin extends Plugin { type: PluginType.Control; + execute: (...args: Args) => PluginResult } export type CommandModuleNoPlugins = { @@ -49,9 +42,8 @@ export type EventModulesNoPlugins = { [T in EventType]: Omit; }; -export type AnyPlugin = ControlPlugin | InitPlugin; -export type AnyCommandPlugin = ControlPlugin | InitPlugin; -export type AnyEventPlugin = ControlPlugin | InitPlugin; +export type AnyCommandPlugin = ControlPlugin | InitPlugin<[DefinedCommandModule]>; +export type AnyEventPlugin = ControlPlugin | InitPlugin<[DefinedEventModule]>; export type InputEvent = { [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; diff --git a/src/index.ts b/src/index.ts index 9e450f7e..f4aed37c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; export * from './handler/structures/structxports'; -export * from './handler/plugins/plugin'; +export * from './handler/plugins/index'; export * from './handler/contracts/index'; export { SernEmitter }; export { _const as single, transient as many } from './handler/utilities/functions'; diff --git a/src/types/module.ts b/src/types/module.ts index 22708c15..6ffafb58 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -28,7 +28,7 @@ import type { import { CommandType } from '../handler/structures/enums'; import type { Args, SlashOptions } from './handler'; import type Context from '../handler/structures/context'; -import type { InitPlugin, ControlPlugin } from '../handler/plugins/plugin'; +import type { InitPlugin, ControlPlugin } from '../handler/plugins'; import { EventType } from '../handler/structures/enums'; import type { UserSelectMenuInteraction } from 'discord.js'; From 9c7ddbc1bb262693def52a86a8e1c927f6357da4 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:15:39 -0600 Subject: [PATCH 03/59] feat: update name usage and update dispatchers.ts --- src/handler/events/dispatchers.ts | 145 ++++++------------ src/handler/events/interactionHandler.ts | 93 +++++------ src/handler/events/messageHandler.ts | 22 +-- src/handler/events/observableHandling.ts | 28 ++-- src/handler/events/readyHandler.ts | 24 +-- .../events/userDefinedEventsHandling.ts | 33 ++-- src/handler/plugins/args.ts | 4 +- src/handler/utilities/readFile.ts | 14 +- src/types/module.ts | 29 +--- 9 files changed, 148 insertions(+), 244 deletions(-) diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index 7ca20074..b61230e4 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -1,131 +1,78 @@ import Context from '../structures/context'; -import type { Payload, SlashOptions } from '../../types/handler'; +import type { Args, Payload } from '../../types/handler'; import { arrAsync } from '../utilities/arrAsync'; import { controller } from '../sern'; import type { - ButtonInteraction, - ModalSubmitInteraction, AutocompleteInteraction, ChatInputCommandInteraction, + ClientEvents, Interaction, - UserContextMenuCommandInteraction, - MessageContextMenuCommandInteraction, ClientEvents, } from 'discord.js'; import { SernError } from '../structures/errors'; import treeSearch from '../utilities/treeSearch'; import type { BothCommand, - ButtonCommand, - ContextMenuMsg, - ContextMenuUser, - ModalSubmitCommand, - StringSelectCommand, + CommandModule, + EventModule, + Module, SlashCommand, - UserSelectCommand, - ChannelSelectCommand, - MentionableSelectCommand, - RoleSelectCommand, } from '../../types/module'; import type SernEmitter from '../sernEmitter'; import { EventEmitter } from 'events'; -import type { - DiscordEventCommand, - ExternalEventCommand, - SernEventCommand, -} from '../structures/events'; +import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from '../structures/events'; import * as assert from 'assert'; import { reducePlugins } from '../utilities/functions'; import { concatMap, from, fromEvent, map, of } from 'rxjs'; -import type { MessageComponentInteraction } from 'discord.js'; +import type { CommandArgs, EventArgs } from '../plugins'; +import type { CommandType, EventType, PluginType } from '../structures/enums'; +import type { Message } from 'discord.js'; -export function applicationCommandDispatcher(interaction: Interaction) { - if (interaction.isAutocomplete()) { - return dispatchAutocomplete(interaction); - } else { - const ctx = Context.wrap(interaction as ChatInputCommandInteraction); - const args: ['slash', SlashOptions] = ['slash', ctx.interaction.options]; - return (mod: BothCommand | SlashCommand) => ({ - mod, - execute: () => mod.execute(ctx, args), - eventPluginRes: arrAsync(mod.onEvent.map(plugs => plugs.execute(ctx, args))), - }); - } -} - -export function dispatchAutocomplete(interaction: AutocompleteInteraction) { - return (mod: BothCommand | SlashCommand) => { - const selectedOption = treeSearch(interaction, mod.options); - if (selectedOption !== undefined) { - return { - mod, - execute: () => selectedOption.command.execute(interaction), - eventPluginRes: arrAsync( - selectedOption.command.onEvent.map(e => e.execute(interaction)), - ), - }; - } - throw Error( - SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, - ); +export function dispatcher( + module: Module, + createArgs: () => unknown[], +) { + const args = createArgs(); + return { + module, + execute: () => module.execute(args), + controlResult: () => arrAsync(module.onEvent.map(plugs => plugs.execute(args))), }; } -export function modalCommandDispatcher(interaction: ModalSubmitInteraction) { - return (mod: ModalSubmitCommand) => ({ - mod, - execute: () => mod.execute(interaction), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute(interaction)), - ), - }); +export function commandDispatcher( + module: CommandModule, + createArgs: () => CommandArgs, +) { + return dispatcher(module, createArgs); } -export function buttonCommandDispatcher(interaction: ButtonInteraction) { - return (mod: ButtonCommand) => ({ - mod, - execute: () => mod.execute(interaction), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute(interaction)), - ), - }); +function eventDispatcher( + module: EventModule, + createArgs: () => EventArgs, +) { + return dispatcher(module, createArgs); } -export function selectMenuCommandDispatcher(interaction: MessageComponentInteraction) { - //safe casts because command type runtime check - return ( - mod: - | StringSelectCommand - | UserSelectCommand - | ChannelSelectCommand - | MentionableSelectCommand - | RoleSelectCommand, - ) => ({ - mod, - execute: () => mod.execute(interaction as never), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute(interaction)), - ), - }); +export function contextArgs(i: Interaction | Message) { + const ctx = Context.wrap(i as ChatInputCommandInteraction | Message); + const args = ['slash', ctx.interaction.options]; + return () => [ctx, args] as [Context, ['slash', Args]]; } - -export function ctxMenuUserDispatcher(interaction: UserContextMenuCommandInteraction) { - return (mod: ContextMenuUser) => ({ - mod, - execute: () => mod.execute(interaction), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute(interaction)), - ), - }); +export function interactionArg(interaction : T) { + return () => [interaction] as [T]; } - -export function ctxMenuMsgDispatcher(interaction: MessageContextMenuCommandInteraction) { - return (mod: ContextMenuMsg) => ({ - mod, - execute: () => mod.execute(interaction), - eventPluginRes: arrAsync( - mod.onEvent.map(plugs => plugs.execute(interaction)), - ), - }); +export function dispatchAutocomplete(module: BothCommand | SlashCommand, interaction: AutocompleteInteraction) { + const option = treeSearch(interaction, module.options); + if (option !== undefined) { + return { + module, + execute: () => option.command.execute(interaction), + controlResult: () => arrAsync(option.command.onEvent.map(e => e.execute(interaction))), + }; + } + throw Error( + SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, + ); } export function sernEmitterDispatcher(e: SernEmitter) { diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 3617b6e3..f71ba3d7 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -6,28 +6,19 @@ import { SernError } from '../structures/errors'; import { CommandType, PayloadType } from '../structures/enums'; import { match, P } from 'ts-pattern'; import { - applicationCommandDispatcher, - buttonCommandDispatcher, - ctxMenuMsgDispatcher, - ctxMenuUserDispatcher, - modalCommandDispatcher, - selectMenuCommandDispatcher, + interactionArg, + contextArgs, + commandDispatcher, + dispatchAutocomplete, dispatcher, } from './dispatchers'; -import type { - ButtonInteraction, - ModalSubmitInteraction, - UserContextMenuCommandInteraction, - MessageContextMenuCommandInteraction, -} from 'discord.js'; import { executeModule } from './observableHandling'; import type { CommandModule } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; -import type { MessageComponentInteraction } from 'discord.js'; export default class InteractionHandler extends EventsHandler<{ event: Interaction; - mod: CommandModule; + module: CommandModule; }> { protected override discordEvent: Observable; constructor(wrapper: Wrapper) { @@ -39,8 +30,8 @@ export default class InteractionHandler extends EventsHandler<{ .pipe( map(this.processModules), concatMap( - ({ mod, execute, eventPluginRes }) => - from(eventPluginRes).pipe(map(res => ({ mod, res, execute }))), //resolve all the Results from event plugins + ({ module, execute, controlResult }) => + from(controlResult()).pipe(map(res => ({ module, res, execute }))), //resolve all the Results from event plugins ), concatMap(payload => executeModule(wrapper, payload)), catchError(handleError(this.crashHandler, this.logger)), @@ -55,20 +46,20 @@ export default class InteractionHandler extends EventsHandler<{ this.discordEvent.subscribe({ next: event => { if (event.isMessageComponent()) { - const mod = get(ms => + const module = get(ms => ms.InteractionHandlers[event.componentType].get(event.customId), ); - this.setState({ event, mod }); + this.setState({ event, module }); } else if (event.isCommand() || event.isAutocomplete()) { - const mod = get( + const module = get( ms => ms.ApplicationCommands[event.commandType].get(event.commandName) ?? ms.BothCommands.get(event.commandName), ); - this.setState({ event, mod }); + this.setState({ event, module }); } else if (event.isModalSubmit()) { - const mod = get(ms => ms.ModalSubmit.get(event.customId)); - this.setState({ event, mod }); + const module = get(ms => ms.ModalSubmit.get(event.customId)); + this.setState({ event, module }); } else { throw Error('This interaction is not supported yet'); } @@ -79,49 +70,37 @@ export default class InteractionHandler extends EventsHandler<{ }); } - protected setState(state: { event: Interaction; mod: CommandModule | undefined }): void { - if (state.mod === undefined) { + protected setState(state: { event: Interaction; module: CommandModule | undefined }): void { + if (state.module === undefined) { this.emitter.emit('warning', { type: PayloadType.Warning, reason: 'Found no module for this interaction', }); } else { //if statement above checks already, safe cast - this.payloadSubject.next(state as { event: Interaction; mod: CommandModule }); + this.payloadSubject.next(state as { event: Interaction; module: CommandModule }); } } - protected processModules({ mod, event }: { event: Interaction; mod: CommandModule }) { - return match(mod) - .with( - { type: P.union(CommandType.Slash, CommandType.Both) }, - applicationCommandDispatcher(event), - ) - .with( - { type: CommandType.Modal }, - modalCommandDispatcher(event as ModalSubmitInteraction), - ) - .with({ type: CommandType.Button }, buttonCommandDispatcher(event as ButtonInteraction)) - .with( - { - type: P.union( - CommandType.RoleSelect, - CommandType.StringSelect, - CommandType.UserSelect, - CommandType.MentionableSelect, - CommandType.ChannelSelect, - ), - }, - selectMenuCommandDispatcher(event as MessageComponentInteraction), - ) - .with( - { type: CommandType.CtxUser }, - ctxMenuUserDispatcher(event as UserContextMenuCommandInteraction), - ) - .with( - { type: CommandType.CtxMsg }, - ctxMenuMsgDispatcher(event as MessageContextMenuCommandInteraction), - ) - .otherwise(() => this.crashHandler.crash(Error(SernError.MismatchModule))); + protected processModules({ module, event }: { event: Interaction; module: CommandModule }) { + return match(module) + .with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => { + if(event.isAutocomplete()) { + /** + * Autocomplete is a special case that + * must be handled separately, since it's + * too different from regular command modules + */ + return dispatchAutocomplete(module, event); + } else { + return commandDispatcher(module, contextArgs(event)); + } + }) + .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) + /** + * Every other command module takes a one argument parameter, its corresponding interaction + * this makes this usage safe + */ + .otherwise((mod) => dispatcher(mod, interactionArg(event))); } } diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index e723c4a7..1f2545a4 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -7,7 +7,6 @@ import { fmt } from '../utilities/messageHelpers'; import Context from '../structures/context'; import { CommandType, PayloadType } from '../structures/enums'; import { arrAsync } from '../utilities/arrAsync'; -import { controller } from '../sern'; import type { CommandModule, TextCommand } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; @@ -15,7 +14,7 @@ import type { ModuleStore } from '../structures/moduleStore'; export default class MessageHandler extends EventsHandler<{ ctx: Context; args: ['text', string[]]; - mod: TextCommand; + module: TextCommand; }> { protected discordEvent: Observable; public constructor(protected wrapper: Wrapper) { @@ -24,13 +23,14 @@ export default class MessageHandler extends EventsHandler<{ this.init(); this.payloadSubject .pipe( - switchMap(({ mod, ctx, args }) => { - const res = arrAsync( - mod.onEvent.map(ep => ep.execute([ctx, args], controller)), + switchMap(({ module, ctx, args }) => { + //refactor to model interaction handler usage + const controlResult = arrAsync( + module.onEvent.map(ep => ep.execute(ctx, args)), ); - const execute = () => mod.execute(ctx, args); + const execute = () => module.execute(ctx, args); //resolves the promise and re-emits it back into source - return from(res).pipe(map(res => ({ mod, execute, res }))); + return from(controlResult).pipe(map(res => ({ module, execute, res }))); }), concatMap(payload => executeModule(wrapper, payload)), catchError(handleError(this.crashHandler, this.logger)), @@ -52,13 +52,13 @@ export default class MessageHandler extends EventsHandler<{ return { ctx: Context.wrap(message), args: <['text', string[]]>['text', rest], - mod: get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)), + module: get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)), }; }), concatMap(element => - of(element.mod).pipe( + of(element.module).pipe( isOneOfCorrectModules(CommandType.Text), - map(mod => ({ ...element, mod })), + map(module => ({ ...element, module })), ), ), ) @@ -68,7 +68,7 @@ export default class MessageHandler extends EventsHandler<{ }); } - protected setState(state: { ctx: Context; args: ['text', string[]]; mod: TextCommand }) { + protected setState(state: { ctx: Context; args: ['text', string[]]; module: TextCommand }) { this.payloadSubject.next(state); } } diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index fe4b866d..87efe575 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -5,7 +5,7 @@ import { Result } from 'ts-results-es'; import type { CommandType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; import { PayloadType, PluginType } from '../structures/enums'; -import type { CommandModule, CommandModuleDefs, AnyModule } from '../../types/module'; +import type { CommandModule, CommandModuleDefs, AnyModule, Module } from '../../types/module'; import { _const } from '../utilities/functions'; import type SernEmitter from '../sernEmitter'; import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; @@ -37,8 +37,8 @@ export function ignoreNonBot(prefix: string) { * @param cb */ export function errTap(cb: (err: SernError) => void) { - return (src: Observable>) => - new Observable<{ mod: T; absPath: string }>(subscriber => { + return (src: Observable>) => + new Observable<{ module: T; absPath: string }>(subscriber => { return src.subscribe({ next(value) { if (value.err) { @@ -78,7 +78,7 @@ export function isOneOfCorrectModules(...input export function executeModule( wrapper: Wrapper, payload: { - mod: CommandModule; + module: Module; execute: () => unknown; res: Result[]; }, @@ -94,14 +94,14 @@ export function executeModule( return throwError(() => ({ type: PayloadType.Failure, reason: res.val, - module: payload.mod, + module: payload.module, })); } return of(res.val).pipe( tap(() => emitter.emit('module.activate', { type: PayloadType.Success, - module: payload.mod, + module: payload.module as AnyModule, }), ), ); @@ -110,7 +110,7 @@ export function executeModule( } else { emitter.emit('module.activate', { type: PayloadType.Failure, - module: payload.mod, + module: payload.module as AnyModule, reason: SernError.PluginFailure, }); return of(undefined); @@ -118,17 +118,17 @@ export function executeModule( } export function resolvePlugins({ - mod, + module, cmdPluginRes, }: { - mod: DefinedCommandModule | DefinedEventModule; + module: DefinedCommandModule | DefinedEventModule; cmdPluginRes: { execute: Awaitable>; type: PluginType.Init; }[]; }) { - if (mod.plugins.length === 0) { - return of({ mod, pluginRes: [] }); + if (module.plugins.length === 0) { + return of({ module, pluginRes: [] }); } // modules with no event plugins are ignored in the previous return from(cmdPluginRes).pipe( @@ -138,14 +138,14 @@ export function resolvePlugins({ toArray(), ), ), - map(pluginRes => ({ mod, pluginRes })), + map(pluginRes => ({ module, pluginRes })), ); } export function processPlugins(payload: { - mod: DefinedCommandModule | DefinedEventModule; + module: DefinedCommandModule | DefinedEventModule; absPath: string; }) { const cmdPluginRes = processCommandPlugins(payload); - return of({ mod: payload.mod, cmdPluginRes }); + return of({ module: payload.module, cmdPluginRes }); } diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index e0f7a183..b9ce20be 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -15,10 +15,10 @@ import type { ModuleStore } from '../structures/moduleStore'; import { _const, err, nameOrFilename, ok } from '../utilities/functions'; export default class ReadyHandler extends EventsHandler<{ - mod: DefinedCommandModule; + module: DefinedCommandModule; absPath: string; }> { - protected discordEvent!: Observable<{ mod: CommandModule; absPath: string }>; + protected discordEvent!: Observable<{ module: CommandModule; absPath: string }>; constructor(wrapper: Wrapper) { super(wrapper); const ready$ = fromEvent(this.client, 'ready').pipe(take(1)); @@ -41,33 +41,33 @@ export default class ReadyHandler extends EventsHandler<{ .subscribe(payload => { const allPluginsSuccessful = payload.pluginRes.every(({ execute }) => execute.ok); if (allPluginsSuccessful) { - const res = registerModule(this.modules, payload.mod); + const res = registerModule(this.modules, payload.module); if (res.err) { this.crashHandler.crash(Error(SernError.InvalidModuleType)); } this.emitter.emit('module.register', { type: PayloadType.Success, - module: payload.mod, + module: payload.module, }); } else { this.emitter.emit('module.register', { type: PayloadType.Failure, - module: payload.mod, + module: payload.module, reason: SernError.PluginFailure, }); } }); } - private static intoDefinedModule({ absPath, mod }: { absPath: string; mod: CommandModule }): { + private static intoDefinedModule({ absPath, module }: { absPath: string; module: CommandModule }): { absPath: string; - mod: DefinedCommandModule; + module: DefinedCommandModule; } { return { absPath, - mod: { - name: nameOrFilename(mod.name, absPath), - description: mod?.description ?? '...', - ...mod, + module: { + name: nameOrFilename(module.name, absPath), + description: module?.description ?? '...', + ...module, }, }; } @@ -78,7 +78,7 @@ export default class ReadyHandler extends EventsHandler<{ complete: () => this.payloadSubject.unsubscribe(), }); } - protected setState(state: { absPath: string; mod: DefinedCommandModule }): void { + protected setState(state: { absPath: string; module: DefinedCommandModule }): void { this.payloadSubject.next(state); } } diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index bdde16e0..7ac774e8 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,8 +1,7 @@ import { catchError, concatMap, filter, from, iif, map, of, tap, toArray } from 'rxjs'; import { buildData } from '../utilities/readFile'; -import { controller } from '../sern'; import type { DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; -import { PayloadType, PluginType } from '../structures/enums'; +import { PayloadType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; import { isDiscordEvent, isExternalEvent, isSernEvent } from '../utilities/predicates'; import { errTap, processPlugins, resolvePlugins } from './observableHandling'; @@ -27,10 +26,10 @@ import { handleError } from '../contracts/errorHandling'; export function processCommandPlugins< T extends DefinedCommandModule | DefinedEventModule, >(payload: { - mod: T; + module: T; absPath: string; }) { - return payload.mod.plugins.map(plug => ({ + return payload.module.plugins.map(plug => ({ type: plug.type, execute: plug.execute(payload), })); @@ -45,37 +44,37 @@ export function processEvents({ containerConfig, events }: Wrapper) { ) as [EventEmitter, ErrorHandling, SernEmitter, Logging?]; const lazy = (k: string) => containerConfig.get(k as keyof Dependencies)[0]; const eventStream$ = eventObservable$(events!, sernEmitter); - const emitSuccess$ = (mod: AnyModule) => - of({ type: PayloadType.Failure, module: mod, reason: SernError.PluginFailure }).pipe( + const emitSuccess$ = (module: AnyModule) => + of({ type: PayloadType.Failure, module, reason: SernError.PluginFailure }).pipe( tap(it => sernEmitter.emit('module.register', it)), ); - const emitFailure$ = (mod: AnyModule) => - of({ type: PayloadType.Success, module: mod } as const).pipe( + const emitFailure$ = (module: AnyModule) => + of({ type: PayloadType.Success, module } as const).pipe( tap(it => sernEmitter.emit('module.register', it)), ); const eventCreation$ = eventStream$.pipe( - map(({ mod, absPath }) => ({ - mod: { - name: nameOrFilename(mod.name, absPath), - ...mod, + map(({ module, absPath }) => ({ + module: { + name: nameOrFilename(module.name, absPath), + ...module, } as DefinedEventModule, absPath, })), concatMap(processPlugins), concatMap(resolvePlugins), //Reduces pluginRes (generated from above) into a single boolean - concatMap(({ pluginRes, mod }) => + concatMap(({ pluginRes, module }) => from(pluginRes).pipe( map(pl => pl.execute), toArray(), reducePlugins, - map(success => ({ success, mod })), + map(success => ({ success, module })), ), ), - concatMap(({ success, mod }) => - iif(() => success, emitFailure$(mod), emitSuccess$(mod)).pipe( + concatMap(({ success, module }) => + iif(() => success, emitFailure$(module), emitSuccess$(module)).pipe( filter(res => res.type === PayloadType.Success), - map(() => mod), + map(() => module), ), ), ); diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index 74958bb1..a2084a5e 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -5,7 +5,7 @@ import type { BothCommand, ButtonCommand, ChannelSelectCommand, ContextMenuUser, MentionableSelectCommand, ModalSubmitCommand, - Module, RoleSelectCommand, + RoleSelectCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand, } from '../../types/module'; @@ -100,5 +100,5 @@ export interface InitArgs { absPath: string; } -export type CommandArgs = CommandArgsMatrix[I][J] +export type CommandArgs = CommandArgsMatrix[I][J] export type EventArgs = EventArgsMatrix[I][J] \ No newline at end of file diff --git a/src/handler/utilities/readFile.ts b/src/handler/utilities/readFile.ts index 707689f0..28f56cee 100644 --- a/src/handler/utilities/readFile.ts +++ b/src/handler/utilities/readFile.ts @@ -30,7 +30,7 @@ export const fmtFileName = (n: string) => n.substring(0, n.length - 3); export function buildData(commandDir: string): Observable< Result< { - mod: T; + module: T; absPath: string; }, SernError @@ -40,20 +40,20 @@ export function buildData(commandDir: string): Observable< return from( Promise.all( commands.map(async absPath => { - let mod: T | undefined; + let module: T | undefined; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - mod = require(absPath).default; + module = require(absPath).default; } catch { - mod = (await import(`file:///` + absPath)).default; + module = (await import(`file:///` + absPath)).default; } - if (mod === undefined) { + if (module === undefined) { return Err(SernError.UndefinedModule); } try { - mod = new (mod as unknown as new () => T)(); + module = new (module as unknown as new () => T)(); } catch {} - return Ok({ mod, absPath }); + return Ok({ module, absPath }); }), ), ).pipe(concatAll()); diff --git a/src/types/module.ts b/src/types/module.ts index 6ffafb58..162328dc 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -35,30 +35,26 @@ import type { UserSelectMenuInteraction } from 'discord.js'; export interface Module { type?: CommandType | EventType; name?: string; + onEvent: ControlPlugin[]; + plugins: InitPlugin[]; description?: string; - execute: (...args: any[]) => any; + execute: (...args: any[]) => Awaitable; } export interface TextCommand extends Module { type: CommandType.Text; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; alias?: string[]; execute: (ctx: Context, args: ['text', string[]]) => Awaitable; } export interface SlashCommand extends Module { type: CommandType.Slash; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; options?: SernOptionsData[]; execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; } export interface BothCommand extends Module { type: CommandType.Both; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; alias?: string[]; options?: SernOptionsData[]; execute: (ctx: Context, args: Args) => Awaitable; @@ -66,64 +62,46 @@ export interface BothCommand extends Module { export interface ContextMenuUser extends Module { type: CommandType.CtxUser; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: UserContextMenuCommandInteraction) => Awaitable; } export interface ContextMenuMsg extends Module { type: CommandType.CtxMsg; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: MessageContextMenuCommandInteraction) => Awaitable; } export interface ButtonCommand extends Module { type: CommandType.Button; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: ButtonInteraction) => Awaitable; } export interface StringSelectCommand extends Module { type: CommandType.StringSelect; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: StringSelectMenuInteraction) => Awaitable; } export interface ChannelSelectCommand extends Module { type: CommandType.ChannelSelect; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: ChannelSelectMenuInteraction) => Awaitable; } export interface RoleSelectCommand extends Module { type: CommandType.RoleSelect; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: RoleSelectMenuInteraction) => Awaitable; } export interface MentionableSelectCommand extends Module { type: CommandType.MentionableSelect; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: MentionableSelectMenuInteraction) => Awaitable; } export interface UserSelectCommand extends Module { type: CommandType.UserSelect; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: UserSelectMenuInteraction) => Awaitable; } export interface ModalSubmitCommand extends Module { type: CommandType.Modal; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; execute: (ctx: ModalSubmitInteraction) => Awaitable; } @@ -131,6 +109,7 @@ export interface AutocompleteCommand extends Module { name?: never; description?: never; type?: never; + plugins: never; onEvent: ControlPlugin[]; execute: (ctx: AutocompleteInteraction) => Awaitable; } From 549305d0d933f9eea88f9449d784b87c28d4ed82 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:22:32 -0600 Subject: [PATCH 04/59] fix:naming --- src/handler/plugins/args.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index a2084a5e..d8e1da8f 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -96,7 +96,7 @@ type EventArgsMatrix = { } export interface InitArgs { - mod: T; + module: T; absPath: string; } From 15b411c4e679d1dc713d1d05534ec2b8799e5d45 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:26:54 -0600 Subject: [PATCH 05/59] feat: slightly safer typings than any[] --- src/handler/plugins/plugin.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index eae94ac4..708b4839 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -20,17 +20,17 @@ import type { EventType, PluginType } from '../structures/enums'; import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; export type PluginResult = Awaitable>; -export interface Plugin { +export interface Plugin { type: PluginType; - execute: (...args: Args) => any + execute: (...args: Args) => PluginResult } -export interface InitPlugin extends Plugin { +export interface InitPlugin extends Plugin { type: PluginType.Init; execute: (...args: Args) => PluginResult } -export interface ControlPlugin extends Plugin { +export interface ControlPlugin extends Plugin { type: PluginType.Control; execute: (...args: Args) => PluginResult } From 0c56cac5331d6c8d3a31c2420d08e05df0db591c Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 14:25:22 -0600 Subject: [PATCH 06/59] fix: forgot to destructure arguments --- src/handler/events/dispatchers.ts | 5 ++--- src/handler/events/interactionHandler.ts | 2 +- src/handler/plugins/createPlugin.ts | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index b61230e4..29c59925 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -26,7 +26,6 @@ import { concatMap, from, fromEvent, map, of } from 'rxjs'; import type { CommandArgs, EventArgs } from '../plugins'; import type { CommandType, EventType, PluginType } from '../structures/enums'; import type { Message } from 'discord.js'; - export function dispatcher( module: Module, createArgs: () => unknown[], @@ -34,8 +33,8 @@ export function dispatcher( const args = createArgs(); return { module, - execute: () => module.execute(args), - controlResult: () => arrAsync(module.onEvent.map(plugs => plugs.execute(args))), + execute: () => module.execute(...args), + controlResult: () => arrAsync(module.onEvent.map(plugs => plugs.execute(...args))), }; } diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index f71ba3d7..9d3ead44 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -84,6 +84,7 @@ export default class InteractionHandler extends EventsHandler<{ protected processModules({ module, event }: { event: Interaction; module: CommandModule }) { return match(module) + .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) .with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => { if(event.isAutocomplete()) { /** @@ -96,7 +97,6 @@ export default class InteractionHandler extends EventsHandler<{ return commandDispatcher(module, contextArgs(event)); } }) - .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) /** * Every other command module takes a one argument parameter, its corresponding interaction * this makes this usage safe diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index b73b8c4e..5f2b04fc 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -2,10 +2,10 @@ import { CommandType, EventType, PluginType } from '../structures/enums'; import type { Plugin, PluginResult } from './plugin'; import type { CommandArgs, EventArgs } from './args'; -export function makePlugin( - type: T, +export function makePlugin( + type: PluginType, execute: (...args: any[]) => any -): Plugin { +): Plugin { return { type, execute From e00bd5eceff775bb4b06779d5f9d5548631cc6e7 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 15:05:14 -0600 Subject: [PATCH 07/59] feat: add special function --- src/handler/plugins/createPlugin.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index 5f2b04fc..9b3ca002 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -1,6 +1,7 @@ import { CommandType, EventType, PluginType } from '../structures/enums'; import type { Plugin, PluginResult } from './plugin'; import type { CommandArgs, EventArgs } from './args'; +import type { ClientEvents } from 'discord.js'; export function makePlugin( type: PluginType, @@ -34,4 +35,13 @@ export function EventControlPlugin( execute: (...args: EventArgs) => PluginResult ) { return makePlugin(PluginType.Control, execute); +} + +/** + * @Experimental + * A specialized function for creating control plugins with discord.js ClientEvents. + * Will probably be moved one day! + */ +export function DiscordEventControlPlugin(name: T, execute: (...args : ClientEvents[T]) => PluginResult) { + return makePlugin(PluginType.Control, execute); } \ No newline at end of file From d6b11e01269297db56642e2224e28c770e921412 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 8 Jan 2023 15:13:43 -0600 Subject: [PATCH 08/59] fix: typings --- src/handler/plugins/args.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index d8e1da8f..5812ae92 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -1,6 +1,6 @@ import type { CommandType } from '../structures/enums'; import type { PluginType } from '../structures/enums'; -import type { ClientEvents, Message } from 'discord.js'; +import type { ClientEvents } from 'discord.js'; import type { BothCommand, ButtonCommand, ChannelSelectCommand, @@ -31,7 +31,7 @@ import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from type CommandArgsMatrix = { [CommandType.Text]: { - [PluginType.Control] : /* library coupled */ [Message] + [PluginType.Control] : [Context, ['text', string[]]] [PluginType.Init] : [InitArgs>] }; [CommandType.Slash]: { @@ -39,7 +39,7 @@ type CommandArgsMatrix = { [PluginType.Init] : [InitArgs>] }; [CommandType.Both]: { - [PluginType.Control] : [Context, ['slash'|'text', /* library coupled */ Args]] + [PluginType.Control] : [Context, Args] [PluginType.Init] : [InitArgs>] }; [CommandType.CtxMsg]: { From b4d7609694089c5f050d38828cac751c044a5824 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 00:19:56 -0600 Subject: [PATCH 09/59] feat: SUPER SIMPLIFY!!! --- src/handler/events/dispatchers.ts | 144 ++++++------------ src/handler/events/observableHandling.ts | 4 +- .../events/userDefinedEventsHandling.ts | 55 ++----- src/handler/utilities/functions.ts | 5 + 4 files changed, 72 insertions(+), 136 deletions(-) diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index 29c59925..bf80f847 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -1,31 +1,22 @@ import Context from '../structures/context'; -import type { Args, Payload } from '../../types/handler'; +import type { DefinedEventModule, SlashOptions } from '../../types/handler'; import { arrAsync } from '../utilities/arrAsync'; -import { controller } from '../sern'; import type { AutocompleteInteraction, ChatInputCommandInteraction, - ClientEvents, Interaction, + Message, } from 'discord.js'; import { SernError } from '../structures/errors'; import treeSearch from '../utilities/treeSearch'; -import type { - BothCommand, - CommandModule, - EventModule, - Module, - SlashCommand, -} from '../../types/module'; -import type SernEmitter from '../sernEmitter'; +import type { BothCommand, CommandModule, Module, SlashCommand } from '../../types/module'; import { EventEmitter } from 'events'; -import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from '../structures/events'; import * as assert from 'assert'; import { reducePlugins } from '../utilities/functions'; -import { concatMap, from, fromEvent, map, of } from 'rxjs'; -import type { CommandArgs, EventArgs } from '../plugins'; -import type { CommandType, EventType, PluginType } from '../structures/enums'; -import type { Message } from 'discord.js'; +import { concatMap, fromEvent, map, Observable, of } from 'rxjs'; +import type { CommandArgs } from '../plugins'; +import type { CommandType, PluginType } from '../structures/enums'; + export function dispatcher( module: Module, createArgs: () => unknown[], @@ -45,21 +36,57 @@ export function commandDispatcher( return dispatcher(module, createArgs); } -function eventDispatcher( - module: EventModule, - createArgs: () => EventArgs, +/** + * Creates an observable from { source } + * @param module + * @param source + */ +export function eventDispatcher( + module: DefinedEventModule, + source: unknown, ) { - return dispatcher(module, createArgs); + assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); + const arrayifySource$ = (src: Observable) => src.pipe(map(event => Array.isArray(event) ? event : [event])); + const promisifiedPlugins = (args: any[]) => module.onEvent.map(plugin => plugin.execute(...args)); + const createResult$ = (src: Observable) => { + if(module.onEvent.length > 0) { + return src.pipe( + concatMap(args => of(args) + .pipe( + //Awaits all the plugins and executes them, + concatMap(args => Promise.all(promisifiedPlugins(args))), + reducePlugins, + map(success => ({ success, args })) + ) + ), + ); + } else { + return src.pipe(map(args => ({ success: true, args }))); + } + }; + const execute$ = (src: Observable<{ success: boolean, args: any[] }>) => src.pipe( + concatMap(({success, args}) => + Promise.resolve(success ? module.execute(...args) : null) + ) + ); + return fromEvent(source, module.name) + .pipe( + arrayifySource$, + createResult$, + execute$ + ); } export function contextArgs(i: Interaction | Message) { const ctx = Context.wrap(i as ChatInputCommandInteraction | Message); const args = ['slash', ctx.interaction.options]; - return () => [ctx, args] as [Context, ['slash', Args]]; + return () => [ctx, args] as [Context, ['slash', SlashOptions]]; } -export function interactionArg(interaction : T) { + +export function interactionArg(interaction: T) { return () => [interaction] as [T]; } + export function dispatchAutocomplete(module: BothCommand | SlashCommand, interaction: AutocompleteInteraction) { const option = treeSearch(interaction, module.options); if (option !== undefined) { @@ -73,76 +100,3 @@ export function dispatchAutocomplete(module: BothCommand | SlashCommand, interac SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, ); } - -export function sernEmitterDispatcher(e: SernEmitter) { - return (cmd: SernEventCommand & { name: string }) => ({ - source: e, - cmd, - execute: fromEvent(e, cmd.name).pipe( - map(event => ({ - event, - executeEvent: of(event).pipe( - concatMap(event => - reducePlugins( - from( - arrAsync( - cmd.onEvent.map(plug => plug.execute(event as Payload)), - ), - ), - ), - ), - ), - })), - ), - }); -} - -export function discordEventDispatcher(e: EventEmitter) { - return (cmd: DiscordEventCommand & { name: string }) => ({ - source: e, - cmd, - execute: fromEvent(e, cmd.name).pipe( - map(event => ({ - event, - executeEvent: of(event).pipe( - concatMap(event => - reducePlugins( - from( - arrAsync( - cmd.onEvent.map(plug => plug.execute(...event as ClientEvents[keyof ClientEvents])), - ), - ), - ), - ), - ), - })), - ), - }); -} - -export function externalEventDispatcher(e: (e: ExternalEventCommand) => unknown) { - return (cmd: ExternalEventCommand & { name: string }) => { - const external = e(cmd); - assert.ok(external instanceof EventEmitter, `${e} is not an EventEmitter`); - return { - source: external, - cmd, - execute: fromEvent(external, cmd.name).pipe( - map(event => ({ - event, - executeEvent: of(event).pipe( - concatMap(event => - reducePlugins( - from( - arrAsync( - cmd.onEvent.map(plug => plug.execute(event, controller)), - ), - ), - ), - ), - ), - })), - ), - }; - }; -} diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 87efe575..7fa4dfa3 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -142,8 +142,8 @@ export function resolvePlugins({ ); } -export function processPlugins(payload: { - module: DefinedCommandModule | DefinedEventModule; +export function processPlugins(payload: { + module: T; absPath: string; }) { const cmdPluginRes = processCommandPlugins(payload); diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 7ac774e8..20ede9c5 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,31 +1,25 @@ import { catchError, concatMap, filter, from, iif, map, of, tap, toArray } from 'rxjs'; import { buildData } from '../utilities/readFile'; import type { DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; -import { PayloadType } from '../structures/enums'; +import { EventType, PayloadType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; -import { isDiscordEvent, isExternalEvent, isSernEvent } from '../utilities/predicates'; import { errTap, processPlugins, resolvePlugins } from './observableHandling'; import type { AnyModule, EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; import type SernEmitter from '../sernEmitter'; import { nameOrFilename, reducePlugins } from '../utilities/functions'; import { match } from 'ts-pattern'; -import { - discordEventDispatcher, - externalEventDispatcher, - sernEmitterDispatcher, -} from './dispatchers'; import type { ErrorHandling, Logging } from '../contracts'; import { SernError } from '../structures/errors'; +import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; /** * Utility function to process command plugins for all Modules * @param payload */ -export function processCommandPlugins< - T extends DefinedCommandModule | DefinedEventModule, ->(payload: { +export function processCommandPlugins(payload: { module: T; absPath: string; }) { @@ -36,7 +30,7 @@ export function processCommandPlugins< } export function processEvents({ containerConfig, events }: Wrapper) { - const [client, error, sernEmitter, logging] = containerConfig.get( + const [client, errorHandling, sernEmitter, logging] = containerConfig.get( '@sern/client', '@sern/errors', '@sern/emitter', @@ -78,34 +72,17 @@ export function processEvents({ containerConfig, events }: Wrapper) { ), ), ); - eventCreation$.subscribe(e => { - const payload = match(e) - .when(isSernEvent, sernEmitterDispatcher(sernEmitter)) - .when(isDiscordEvent, discordEventDispatcher(client)) - .when( - isExternalEvent, - externalEventDispatcher(e => lazy(e.emitter)), - ) - .otherwise(() => error.crash(Error(SernError.InvalidModuleType))); - payload.execute - .pipe( - concatMap(({ event, executeEvent }) => - executeEvent.pipe( - tap(success => { - if (success) { - if (Array.isArray(event)) { - payload.cmd.execute(...event); - } else { - payload.cmd.execute(event as never); - } - } - }), - catchError(handleError(error, logging)), - ), - ), - ) - .subscribe(); - }); + const intoDispatcher = (e: DefinedEventModule | DefinedCommandModule) => match(e) + .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) + .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) + .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) + .otherwise(() => errorHandling.crash(Error(SernError.InvalidModuleType))); + + eventCreation$.pipe( + map(intoDispatcher), + tap(dispatcher => dispatcher.subscribe()), + catchError(handleError(errorHandling, logging)) + ).subscribe(); } function eventObservable$(events: string, emitter: SernEmitter) { diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts index 3480a299..f94be3bc 100644 --- a/src/handler/utilities/functions.ts +++ b/src/handler/utilities/functions.ts @@ -42,6 +42,11 @@ export function partition(arr: (T & V)[], condition: (e: T & V) => boolean return [t, v]; } +/** + * Reduces a stream of results into a single boolean value + * possible refactor in future to lazily check? + * @param src + */ export function reducePlugins(src: Observable[]>): Observable { return src.pipe(switchMap(s => of(s.every(a => a.ok)))); } From 9227d783497dfadaa272429aa0d4a193f853f9ea Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 09:31:27 -0600 Subject: [PATCH 10/59] refactor: move promisifiedPlugins closer to call site --- src/handler/events/dispatchers.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index bf80f847..8afccd4b 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -16,6 +16,7 @@ import { reducePlugins } from '../utilities/functions'; import { concatMap, fromEvent, map, Observable, of } from 'rxjs'; import type { CommandArgs } from '../plugins'; import type { CommandType, PluginType } from '../structures/enums'; +import { Err } from 'ts-results-es'; export function dispatcher( module: Module, @@ -46,10 +47,15 @@ export function eventDispatcher( source: unknown, ) { assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); + /** + * Sometimes fromEvent emits a single parameter, which is not an Array. This + * operator function flattens events into an array + * @param src + */ const arrayifySource$ = (src: Observable) => src.pipe(map(event => Array.isArray(event) ? event : [event])); - const promisifiedPlugins = (args: any[]) => module.onEvent.map(plugin => plugin.execute(...args)); const createResult$ = (src: Observable) => { if(module.onEvent.length > 0) { + const promisifiedPlugins = (args: any[]) => module.onEvent.map(plugin => plugin.execute(...args)); return src.pipe( concatMap(args => of(args) .pipe( @@ -66,7 +72,7 @@ export function eventDispatcher( }; const execute$ = (src: Observable<{ success: boolean, args: any[] }>) => src.pipe( concatMap(({success, args}) => - Promise.resolve(success ? module.execute(...args) : null) + Promise.resolve(success ? module.execute(...args) : Err.EMPTY) ) ); return fromEvent(source, module.name) From eb39dc0f77ae7abf3d768b1cfd59590b70ece01e Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:48:33 -0600 Subject: [PATCH 11/59] refactor: typings --- src/handler/events/interactionHandler.ts | 6 ++-- src/handler/events/messageHandler.ts | 2 +- src/handler/events/observableHandling.ts | 29 +++++++++++++++---- src/handler/events/readyHandler.ts | 23 ++++----------- .../events/userDefinedEventsHandling.ts | 14 +++------ src/handler/structures/events.ts | 8 ++--- src/types/module.ts | 4 ++- 7 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 9d3ead44..8979378d 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -29,11 +29,10 @@ export default class InteractionHandler extends EventsHandler<{ this.payloadSubject .pipe( map(this.processModules), - concatMap( - ({ module, execute, controlResult }) => + concatMap(({ module, execute, controlResult }) => from(controlResult()).pipe(map(res => ({ module, res, execute }))), //resolve all the Results from event plugins ), - concatMap(payload => executeModule(wrapper, payload)), + concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) .subscribe(); @@ -85,6 +84,7 @@ export default class InteractionHandler extends EventsHandler<{ protected processModules({ module, event }: { event: Interaction; module: CommandModule }) { return match(module) .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) + //P.union = either CommandType.Slash or CommandType.Both .with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => { if(event.isAutocomplete()) { /** diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index 1f2545a4..d8dd691d 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -32,7 +32,7 @@ export default class MessageHandler extends EventsHandler<{ //resolves the promise and re-emits it back into source return from(controlResult).pipe(map(res => ({ module, execute, res }))); }), - concatMap(payload => executeModule(wrapper, payload)), + concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) .subscribe(); diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 7fa4dfa3..4a7fefcd 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -5,10 +5,10 @@ import { Result } from 'ts-results-es'; import type { CommandType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; import { PayloadType, PluginType } from '../structures/enums'; -import type { CommandModule, CommandModuleDefs, AnyModule, Module } from '../../types/module'; -import { _const } from '../utilities/functions'; +import type { CommandModule, CommandModuleDefs, AnyModule, Module, EventModule } from '../../types/module'; +import { _const, nameOrFilename } from '../utilities/functions'; import type SernEmitter from '../sernEmitter'; -import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule, Processed } from '../../types/handler'; import type { Awaitable } from 'discord.js'; import { processCommandPlugins } from './userDefinedEventsHandling'; @@ -76,14 +76,13 @@ export function isOneOfCorrectModules(...input } export function executeModule( - wrapper: Wrapper, + emitter: SernEmitter, payload: { module: Module; execute: () => unknown; res: Result[]; }, ) { - const emitter = wrapper.containerConfig.get('@sern/emitter')[0] as SernEmitter; if (payload.res.every(el => el.ok)) { const executeFn = Result.wrapAsync(() => Promise.resolve(payload.execute()), @@ -149,3 +148,23 @@ export function processPlugins -> Observable<{ absPath: string; module: Processed }> + */ +export function defineAllFields$( + src: Observable<{ absPath: string; module: T }> +) { + const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ + absPath, + module : { + name : nameOrFilename(module.name, absPath), + description: module.description ?? '...', + ...module + } + }); + return src.pipe( + map(fillFields) + ); +} \ No newline at end of file diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index b9ce20be..f8525089 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -2,7 +2,7 @@ import { EventsHandler } from './eventsHandler'; import type Wrapper from '../structures/wrapper'; import { concatMap, fromEvent, Observable, map, take } from 'rxjs'; import * as Files from '../utilities/readFile'; -import { errTap, processPlugins, resolvePlugins } from './observableHandling'; +import { defineAllFields$, errTap, processPlugins, resolvePlugins } from './observableHandling'; import { CommandType, PayloadType } from '../structures/enums'; import { SernError } from '../structures/errors'; import { match } from 'ts-pattern'; @@ -12,7 +12,7 @@ import type { CommandModule } from '../../types/module'; import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; import type { ModuleManager } from '../contracts'; import type { ModuleStore } from '../structures/moduleStore'; -import { _const, err, nameOrFilename, ok } from '../utilities/functions'; +import { _const, err, ok } from '../utilities/functions'; export default class ReadyHandler extends EventsHandler<{ module: DefinedCommandModule; @@ -58,22 +58,11 @@ export default class ReadyHandler extends EventsHandler<{ } }); } - private static intoDefinedModule({ absPath, module }: { absPath: string; module: CommandModule }): { - absPath: string; - module: DefinedCommandModule; - } { - return { - absPath, - module: { - name: nameOrFilename(module.name, absPath), - description: module?.description ?? '...', - ...module, - }, - }; - } protected init() { - this.discordEvent.pipe(map(ReadyHandler.intoDefinedModule)).subscribe({ + this.discordEvent.pipe( + defineAllFields$ + ).subscribe({ next: value => this.setState(value), complete: () => this.payloadSubject.unsubscribe(), }); @@ -92,7 +81,7 @@ function registerModule( const set = Result.wrap(_const(manager.set(cb))); return set.ok ? ok() : err(); }; - return match(mod) + return match(mod) .with({ type: CommandType.Text }, mod => { mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); return insert(ms => ms.TextCommands.set(name, mod)); diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 20ede9c5..d9cbcf6a 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -3,7 +3,7 @@ import { buildData } from '../utilities/readFile'; import type { DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; import { EventType, PayloadType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; -import { errTap, processPlugins, resolvePlugins } from './observableHandling'; +import { defineAllFields$, errTap, processPlugins, resolvePlugins } from './observableHandling'; import type { AnyModule, EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; import type SernEmitter from '../sernEmitter'; @@ -47,13 +47,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { tap(it => sernEmitter.emit('module.register', it)), ); const eventCreation$ = eventStream$.pipe( - map(({ module, absPath }) => ({ - module: { - name: nameOrFilename(module.name, absPath), - ...module, - } as DefinedEventModule, - absPath, - })), + defineAllFields$, concatMap(processPlugins), concatMap(resolvePlugins), //Reduces pluginRes (generated from above) into a single boolean @@ -74,14 +68,14 @@ export function processEvents({ containerConfig, events }: Wrapper) { ); const intoDispatcher = (e: DefinedEventModule | DefinedCommandModule) => match(e) .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) - .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) + .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) .otherwise(() => errorHandling.crash(Error(SernError.InvalidModuleType))); eventCreation$.pipe( map(intoDispatcher), tap(dispatcher => dispatcher.subscribe()), - catchError(handleError(errorHandling, logging)) + catchError(handleError(errorHandling, logging)), ).subscribe(); } diff --git a/src/handler/structures/events.ts b/src/handler/structures/events.ts index c20ac4a2..ad8961da 100644 --- a/src/handler/structures/events.ts +++ b/src/handler/structures/events.ts @@ -1,4 +1,4 @@ -import type { SernEventsMapping } from '../../types/handler'; +import type { Payload, SernEventsMapping } from '../../types/handler'; import type { ControlPlugin, InitPlugin, @@ -13,7 +13,7 @@ export interface SernEventCommand; + execute(args: Payload): Awaitable; } export interface DiscordEventCommand @@ -22,7 +22,7 @@ export interface DiscordEventCommand; + execute(args: ClientEvents[T]): Awaitable; } export interface ExternalEventCommand extends Module { @@ -31,5 +31,5 @@ export interface ExternalEventCommand extends Module { type: EventType.External; onEvent: ControlPlugin[]; plugins: InitPlugin[]; - execute(...args: unknown[]): Awaitable; + execute(args: unknown[]): Awaitable; } diff --git a/src/types/module.ts b/src/types/module.ts index 162328dc..8d01e085 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -38,7 +38,7 @@ export interface Module { onEvent: ControlPlugin[]; plugins: InitPlugin[]; description?: string; - execute: (...args: any[]) => Awaitable; + execute: (...args: any[]) => Awaitable; } export interface TextCommand extends Module { @@ -49,6 +49,7 @@ export interface TextCommand extends Module { export interface SlashCommand extends Module { type: CommandType.Slash; + description: string, options?: SernOptionsData[]; execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; } @@ -56,6 +57,7 @@ export interface SlashCommand extends Module { export interface BothCommand extends Module { type: CommandType.Both; alias?: string[]; + description: string, options?: SernOptionsData[]; execute: (ctx: Context, args: Args) => Awaitable; } From eaee691ae759d6770688b0f246c4080372e5bb4b Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:57:02 -0600 Subject: [PATCH 12/59] refactor: typings --- src/handler/structures/events.ts | 16 +++------------- src/types/module.ts | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/handler/structures/events.ts b/src/handler/structures/events.ts index ad8961da..faaaa09a 100644 --- a/src/handler/structures/events.ts +++ b/src/handler/structures/events.ts @@ -1,8 +1,4 @@ import type { Payload, SernEventsMapping } from '../../types/handler'; -import type { - ControlPlugin, - InitPlugin, -} from '../plugins/plugin'; import type { Awaitable, ClientEvents } from 'discord.js'; import type { EventType } from './enums'; import type { Module } from '../../types/module'; @@ -11,25 +7,19 @@ export interface SernEventCommand; + execute(...args: [Payload]): Awaitable; } export interface DiscordEventCommand extends Module { name?: T; type: EventType.Discord; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; - execute(args: ClientEvents[T]): Awaitable; + execute(...args: ClientEvents[T]): Awaitable; } export interface ExternalEventCommand extends Module { name?: string; emitter: string; type: EventType.External; - onEvent: ControlPlugin[]; - plugins: InitPlugin[]; - execute(args: unknown[]): Awaitable; + execute(...args: unknown[]): Awaitable; } diff --git a/src/types/module.ts b/src/types/module.ts index 8d01e085..bbff1708 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -38,7 +38,7 @@ export interface Module { onEvent: ControlPlugin[]; plugins: InitPlugin[]; description?: string; - execute: (...args: any[]) => Awaitable; + execute: (...args: any[]) => any; } export interface TextCommand extends Module { From a780508fbb91241fc6d5cf2bfdb193ea6eb22f8d Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 13:59:28 -0600 Subject: [PATCH 13/59] refactor: consolidate resolving initplugins into one function --- src/handler/events/dispatchers.ts | 7 +- src/handler/events/observableHandling.ts | 82 ++++++++++--------- src/handler/events/readyHandler.ts | 15 ++-- .../events/userDefinedEventsHandling.ts | 29 ++----- src/handler/utilities/functions.ts | 9 +- 5 files changed, 63 insertions(+), 79 deletions(-) diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index 8afccd4b..68eabe2f 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -12,11 +12,12 @@ import treeSearch from '../utilities/treeSearch'; import type { BothCommand, CommandModule, Module, SlashCommand } from '../../types/module'; import { EventEmitter } from 'events'; import * as assert from 'assert'; -import { reducePlugins } from '../utilities/functions'; +import { reduceResults$ } from './observableHandling'; import { concatMap, fromEvent, map, Observable, of } from 'rxjs'; import type { CommandArgs } from '../plugins'; import type { CommandType, PluginType } from '../structures/enums'; import { Err } from 'ts-results-es'; +import { isEmpty } from '../utilities/functions'; export function dispatcher( module: Module, @@ -54,14 +55,14 @@ export function eventDispatcher( */ const arrayifySource$ = (src: Observable) => src.pipe(map(event => Array.isArray(event) ? event : [event])); const createResult$ = (src: Observable) => { - if(module.onEvent.length > 0) { + if(isEmpty(module.onEvent)) { const promisifiedPlugins = (args: any[]) => module.onEvent.map(plugin => plugin.execute(...args)); return src.pipe( concatMap(args => of(args) .pipe( //Awaits all the plugins and executes them, concatMap(args => Promise.all(promisifiedPlugins(args))), - reducePlugins, + reduceResults$, map(success => ({ success, args })) ) ), diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 4a7fefcd..bde7dc18 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,16 +1,15 @@ import type { Message } from 'discord.js'; -import { concatMap, from, map, Observable, of, switchMap, tap, throwError, toArray } from 'rxjs'; +import { concatMap, from, map, Observable, of, switchMap, tap, throwError } from 'rxjs'; import { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; import type { CommandType } from '../structures/enums'; -import type Wrapper from '../structures/wrapper'; -import { PayloadType, PluginType } from '../structures/enums'; -import type { CommandModule, CommandModuleDefs, AnyModule, Module, EventModule } from '../../types/module'; -import { _const, nameOrFilename } from '../utilities/functions'; +import { PayloadType } from '../structures/enums'; +import type { AnyModule, CommandModule, CommandModuleDefs, EventModule, Module } from '../../types/module'; +import { _const, isEmpty, nameOrFilename } from '../utilities/functions'; import type SernEmitter from '../sernEmitter'; -import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule, Processed } from '../../types/handler'; -import type { Awaitable } from 'discord.js'; +import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule } from '../../types/handler'; import { processCommandPlugins } from './userDefinedEventsHandling'; +import type { PluginResult } from '../plugins'; export function ignoreNonBot(prefix: string) { return (src: Observable) => @@ -116,55 +115,60 @@ export function executeModule( } } -export function resolvePlugins({ - module, - cmdPluginRes, -}: { - module: DefinedCommandModule | DefinedEventModule; - cmdPluginRes: { - execute: Awaitable>; - type: PluginType.Init; - }[]; -}) { - if (module.plugins.length === 0) { - return of({ module, pluginRes: [] }); - } - // modules with no event plugins are ignored in the previous - return from(cmdPluginRes).pipe( - switchMap(pl => - from(pl.execute).pipe( - map(execute => ({ ...pl, execute })), - toArray(), - ), - ), - map(pluginRes => ({ module, pluginRes })), +/** + * Plugins are successful if all results are ok. + * Reduces initResult into a single boolean + * @param src + */ +export function resolveInitPlugins$( + src: Observable<{ module: T, initResult: PluginResult[], }>, +) { + return src.pipe( + concatMap(({ module, initResult }) => { + if (isEmpty(initResult)) + return of({ module, success: true }); + else + return from(Promise.all(initResult)).pipe( + reduceResults$, + map(success => ({ module, success })), + ); + }), ); } -export function processPlugins(payload: { +export function processPlugins(payload: { module: T; absPath: string; }) { - const cmdPluginRes = processCommandPlugins(payload); - return of({ module: payload.module, cmdPluginRes }); + const initResult = processCommandPlugins(payload); + return of({ module: payload.module, initResult }); } /** * fills the defaults for modules * signature : Observable<{ absPath: string; module: CommandModule | EventModule }> -> Observable<{ absPath: string; module: Processed }> */ -export function defineAllFields$( - src: Observable<{ absPath: string; module: T }> +export function defineAllFields$( + src: Observable<{ absPath: string; module: T }>, ) { const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ absPath, - module : { - name : nameOrFilename(module.name, absPath), + module: { + name: nameOrFilename(module.name, absPath), description: module.description ?? '...', - ...module - } + ...module, + }, }); return src.pipe( - map(fillFields) + map(fillFields), ); +} + +/** + * Reduces a stream of results into a single boolean value + * possible refactor in future to lazily check? + * @param src + */ +export function reduceResults$(src: Observable[]>): Observable { + return src.pipe(switchMap(s => of(s.every(a => a.ok)))); } \ No newline at end of file diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index f8525089..7b86784a 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -2,7 +2,7 @@ import { EventsHandler } from './eventsHandler'; import type Wrapper from '../structures/wrapper'; import { concatMap, fromEvent, Observable, map, take } from 'rxjs'; import * as Files from '../utilities/readFile'; -import { defineAllFields$, errTap, processPlugins, resolvePlugins } from './observableHandling'; +import { defineAllFields$, errTap, processPlugins, resolveInitPlugins$ } from './observableHandling'; import { CommandType, PayloadType } from '../structures/enums'; import { SernError } from '../structures/errors'; import { match } from 'ts-pattern'; @@ -37,22 +37,21 @@ export default class ReadyHandler extends EventsHandler<{ ); this.init(); this.payloadSubject - .pipe(concatMap(processPlugins), concatMap(resolvePlugins)) - .subscribe(payload => { - const allPluginsSuccessful = payload.pluginRes.every(({ execute }) => execute.ok); - if (allPluginsSuccessful) { - const res = registerModule(this.modules, payload.module); + .pipe(concatMap(processPlugins), resolveInitPlugins$) + .subscribe(({success, module }) => { + if (success) { + const res = registerModule(this.modules, module); if (res.err) { this.crashHandler.crash(Error(SernError.InvalidModuleType)); } this.emitter.emit('module.register', { type: PayloadType.Success, - module: payload.module, + module }); } else { this.emitter.emit('module.register', { type: PayloadType.Failure, - module: payload.module, + module, reason: SernError.PluginFailure, }); } diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index d9cbcf6a..ba1ac87c 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,13 +1,12 @@ import { catchError, concatMap, filter, from, iif, map, of, tap, toArray } from 'rxjs'; import { buildData } from '../utilities/readFile'; -import type { DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; +import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; import { EventType, PayloadType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; -import { defineAllFields$, errTap, processPlugins, resolvePlugins } from './observableHandling'; +import { defineAllFields$, errTap, processPlugins, reduceResults$, resolveInitPlugins$ } from './observableHandling'; import type { AnyModule, EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; import type SernEmitter from '../sernEmitter'; -import { nameOrFilename, reducePlugins } from '../utilities/functions'; import { match } from 'ts-pattern'; import type { ErrorHandling, Logging } from '../contracts'; import { SernError } from '../structures/errors'; @@ -18,15 +17,10 @@ import { handleError } from '../contracts/errorHandling'; * Utility function to process command plugins for all Modules * @param payload */ -export function processCommandPlugins(payload: { - module: T; - absPath: string; -}) { - return payload.module.plugins.map(plug => ({ - type: plug.type, - execute: plug.execute(payload), - })); +export function processCommandPlugins( + payload: { module: T; absPath: string; } +) { + return payload.module.plugins.map(plug => plug.execute(payload)); } export function processEvents({ containerConfig, events }: Wrapper) { @@ -49,16 +43,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { const eventCreation$ = eventStream$.pipe( defineAllFields$, concatMap(processPlugins), - concatMap(resolvePlugins), - //Reduces pluginRes (generated from above) into a single boolean - concatMap(({ pluginRes, module }) => - from(pluginRes).pipe( - map(pl => pl.execute), - toArray(), - reducePlugins, - map(success => ({ success, module })), - ), - ), + resolveInitPlugins$, concatMap(({ success, module }) => iif(() => success, emitFailure$(module), emitSuccess$(module)).pipe( filter(res => res.type === PayloadType.Success), diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts index f94be3bc..64c6cbb7 100644 --- a/src/handler/utilities/functions.ts +++ b/src/handler/utilities/functions.ts @@ -42,11 +42,6 @@ export function partition(arr: (T & V)[], condition: (e: T & V) => boolean return [t, v]; } -/** - * Reduces a stream of results into a single boolean value - * possible refactor in future to lazily check? - * @param src - */ -export function reducePlugins(src: Observable[]>): Observable { - return src.pipe(switchMap(s => of(s.every(a => a.ok)))); +export function isEmpty(array: unknown[]) : boolean { + return array.length === 0; } From 2e568e7a190d226a5843504abc8d50f701310fd8 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:19:23 -0600 Subject: [PATCH 14/59] refactor: better types --- src/handler/events/observableHandling.ts | 4 ++-- src/handler/events/userDefinedEventsHandling.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index bde7dc18..e5c56a82 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -4,10 +4,10 @@ import { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; import type { CommandType } from '../structures/enums'; import { PayloadType } from '../structures/enums'; -import type { AnyModule, CommandModule, CommandModuleDefs, EventModule, Module } from '../../types/module'; +import type { AnyModule, CommandModule, CommandModuleDefs, Module } from '../../types/module'; import { _const, isEmpty, nameOrFilename } from '../utilities/functions'; import type SernEmitter from '../sernEmitter'; -import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +import type { AnyDefinedModule } from '../../types/handler'; import { processCommandPlugins } from './userDefinedEventsHandling'; import type { PluginResult } from '../plugins'; diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index ba1ac87c..0007be1e 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,6 +1,6 @@ -import { catchError, concatMap, filter, from, iif, map, of, tap, toArray } from 'rxjs'; +import { catchError, concatMap, filter, iif, map, of, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; -import type { AnyDefinedModule, DefinedCommandModule, DefinedEventModule, Dependencies } from '../../types/handler'; +import type { AnyDefinedModule, Dependencies } from '../../types/handler'; import { EventType, PayloadType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; import { defineAllFields$, errTap, processPlugins, reduceResults$, resolveInitPlugins$ } from './observableHandling'; @@ -51,7 +51,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { ), ), ); - const intoDispatcher = (e: DefinedEventModule | DefinedCommandModule) => match(e) + const intoDispatcher = (e: AnyDefinedModule) => match(e) .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) From 74a5726970e3f9050183dcda2217c015890ddfb6 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:49:48 -0600 Subject: [PATCH 15/59] revert: remove unneeded function --- src/handler/utilities/arrAsync.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/handler/utilities/arrAsync.ts diff --git a/src/handler/utilities/arrAsync.ts b/src/handler/utilities/arrAsync.ts deleted file mode 100644 index b12aec22..00000000 --- a/src/handler/utilities/arrAsync.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Awaitable } from 'discord.js'; - -export async function arrAsync(promiseLike: Awaitable[]): Promise { - const arr: T[] = []; - for await (const el of promiseLike) { - arr.push(el); - } - return arr; -} From 7f22946a13aa827ad338c4cd737b42f048a89679 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:49:53 -0600 Subject: [PATCH 16/59] revert: remove unneeded function --- src/handler/utilities/predicates.ts | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/handler/utilities/predicates.ts diff --git a/src/handler/utilities/predicates.ts b/src/handler/utilities/predicates.ts deleted file mode 100644 index b38d3729..00000000 --- a/src/handler/utilities/predicates.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { - DiscordEventCommand, - ExternalEventCommand, - SernEventCommand, -} from '../structures/events'; -import { CommandModule, EventType } from '../..'; -import type { AnyModule, CommandModuleDefs, EventModule } from '../../types/module'; - -export function correctModuleType( - plug: AnyModule | undefined, - type: T, -): plug is CommandModuleDefs[T] { - // Another way to check if type is equivalent, - // It will check based on flag system instead - return plug !== undefined && (plug.type & type) !== 0; -} - -export function isDiscordEvent(el: EventModule | CommandModule): el is DiscordEventCommand { - return el.type === EventType.Discord; -} -export function isSernEvent(el: EventModule | CommandModule): el is SernEventCommand { - return el.type === EventType.Sern; -} -export function isExternalEvent(el: EventModule | CommandModule): el is ExternalEventCommand { - return el.type === EventType.External && 'emitter' in el; -} From e642a49bad55e092630fcff5278c568323cf62d9 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:50:52 -0600 Subject: [PATCH 17/59] feat: dispatch work, simplify --- src/handler/events/dispatchers.ts | 109 ------------------ src/handler/events/dispatchers/dispatchers.ts | 70 +++++++++++ 2 files changed, 70 insertions(+), 109 deletions(-) delete mode 100644 src/handler/events/dispatchers.ts create mode 100644 src/handler/events/dispatchers/dispatchers.ts diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts deleted file mode 100644 index 68eabe2f..00000000 --- a/src/handler/events/dispatchers.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Context from '../structures/context'; -import type { DefinedEventModule, SlashOptions } from '../../types/handler'; -import { arrAsync } from '../utilities/arrAsync'; -import type { - AutocompleteInteraction, - ChatInputCommandInteraction, - Interaction, - Message, -} from 'discord.js'; -import { SernError } from '../structures/errors'; -import treeSearch from '../utilities/treeSearch'; -import type { BothCommand, CommandModule, Module, SlashCommand } from '../../types/module'; -import { EventEmitter } from 'events'; -import * as assert from 'assert'; -import { reduceResults$ } from './observableHandling'; -import { concatMap, fromEvent, map, Observable, of } from 'rxjs'; -import type { CommandArgs } from '../plugins'; -import type { CommandType, PluginType } from '../structures/enums'; -import { Err } from 'ts-results-es'; -import { isEmpty } from '../utilities/functions'; - -export function dispatcher( - module: Module, - createArgs: () => unknown[], -) { - const args = createArgs(); - return { - module, - execute: () => module.execute(...args), - controlResult: () => arrAsync(module.onEvent.map(plugs => plugs.execute(...args))), - }; -} - -export function commandDispatcher( - module: CommandModule, - createArgs: () => CommandArgs, -) { - return dispatcher(module, createArgs); -} - -/** - * Creates an observable from { source } - * @param module - * @param source - */ -export function eventDispatcher( - module: DefinedEventModule, - source: unknown, -) { - assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); - /** - * Sometimes fromEvent emits a single parameter, which is not an Array. This - * operator function flattens events into an array - * @param src - */ - const arrayifySource$ = (src: Observable) => src.pipe(map(event => Array.isArray(event) ? event : [event])); - const createResult$ = (src: Observable) => { - if(isEmpty(module.onEvent)) { - const promisifiedPlugins = (args: any[]) => module.onEvent.map(plugin => plugin.execute(...args)); - return src.pipe( - concatMap(args => of(args) - .pipe( - //Awaits all the plugins and executes them, - concatMap(args => Promise.all(promisifiedPlugins(args))), - reduceResults$, - map(success => ({ success, args })) - ) - ), - ); - } else { - return src.pipe(map(args => ({ success: true, args }))); - } - }; - const execute$ = (src: Observable<{ success: boolean, args: any[] }>) => src.pipe( - concatMap(({success, args}) => - Promise.resolve(success ? module.execute(...args) : Err.EMPTY) - ) - ); - return fromEvent(source, module.name) - .pipe( - arrayifySource$, - createResult$, - execute$ - ); -} - -export function contextArgs(i: Interaction | Message) { - const ctx = Context.wrap(i as ChatInputCommandInteraction | Message); - const args = ['slash', ctx.interaction.options]; - return () => [ctx, args] as [Context, ['slash', SlashOptions]]; -} - -export function interactionArg(interaction: T) { - return () => [interaction] as [T]; -} - -export function dispatchAutocomplete(module: BothCommand | SlashCommand, interaction: AutocompleteInteraction) { - const option = treeSearch(interaction, module.options); - if (option !== undefined) { - return { - module, - execute: () => option.command.execute(interaction), - controlResult: () => arrAsync(option.command.onEvent.map(e => e.execute(interaction))), - }; - } - throw Error( - SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, - ); -} diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts new file mode 100644 index 00000000..ff57d0c1 --- /dev/null +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -0,0 +1,70 @@ +import type { DefinedEventModule } from '../../../types/handler'; +import type { + AutocompleteInteraction, +} from 'discord.js'; +import { SernError } from '../../structures/errors'; +import treeSearch from '../../utilities/treeSearch'; +import type { BothCommand, Module, SlashCommand } from '../../../types/module'; +import { EventEmitter } from 'events'; +import * as assert from 'assert'; +import { createPluginResolver } from '../observableHandling'; +import { concatMap, from, fromEvent, map, Observable } from 'rxjs'; +import { callPlugin$ } from '../operators'; + +export function dispatchCommand( + module: Module, + createArgs: () => unknown[], +) { + const args = createArgs(); + return { + module, + args, + }; +} + +/** + * Creates an observable from { source } + * @param module + * @param source + */ +export function eventDispatcher( + module: DefinedEventModule, + source: unknown, +) { + assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); + /** + * Sometimes fromEvent emits a single parameter, which is not an Array. This + * operator function flattens events into an array + * @param src + */ + const arrayifySource$ = (src: Observable) => src.pipe( + map(event => Array.isArray(event) ? event : [event]), + map( args => ({ module, args })) + ); + const createResult$ = createPluginResolver({ + onSuccess: ({ args } ) => args, + createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin$(args)), + }); + const execute$ = (src: Observable) => src.pipe( + concatMap(async args => module.execute(...args)) + ); + return fromEvent(source, module.name) + .pipe( + arrayifySource$, + concatMap(createResult$), + execute$ + ); +} + +export function dispatchAutocomplete(module: BothCommand | SlashCommand, interaction: AutocompleteInteraction) { + const option = treeSearch(interaction, module.options); + if (option !== undefined) { + return { + module, + args: [interaction], + }; + } + throw Error( + SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, + ); +} From 079554ed0e16c32635c722ac15e197638271d2da Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:51:17 -0600 Subject: [PATCH 18/59] feat: move some observableHandling function to operators for clarity --- src/handler/events/operators/index.ts | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/handler/events/operators/index.ts diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts new file mode 100644 index 00000000..d4047a5c --- /dev/null +++ b/src/handler/events/operators/index.ts @@ -0,0 +1,62 @@ +import { concatMap, defaultIfEmpty, EMPTY, every, map, Observable, of } from 'rxjs'; +import type { ControlPlugin, InitPlugin } from '../../plugins'; +import type { AnyModule } from '../../../types/module'; +import { nameOrFilename } from '../../utilities/functions'; +import type { Result } from 'ts-results-es'; + +/** + * if {src} is true, mapTo V, else ignore + * @param item + */ +export function filterMapTo$(item: () => V) { + return (src: Observable) => src.pipe( + concatMap( shouldKeep => + shouldKeep ? of(item()) : EMPTY + ) + ); +} + +/** + * Calls any plugin with {args}. + * @param args if an array, its spread and plugin called. + */ +export function callPlugin$(args: V) { + return (src: Observable) => + src.pipe(concatMap(async plugin => { + if (Array.isArray(args)) { + return plugin.execute(...args); + } + return plugin.execute(args); + })); +} + +/** + * fills the defaults for modules + * signature : Observable<{ absPath: string; module: CommandModule | EventModule }> -> Observable<{ absPath: string; module: Processed }> + */ +export function defineAllFields$( + src: Observable<{ absPath: string; module: T }>, +) { + const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ + absPath, + module: { + name: nameOrFilename(module.name, absPath), + description: module.description ?? '...', + ...module, + } + }); + return src.pipe( + map(fillFields), + ); +} + +/** + * Checks if the stream of results is all ok. + * @param src + */ +export function everyPluginOk$(src: Observable>) { + return src.pipe( + every(result => result.ok), + defaultIfEmpty(true), + ); +} \ No newline at end of file From ea49b69e96794316d1823d911a7e9938c7baeab5 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:52:18 -0600 Subject: [PATCH 19/59] feat: simplify and document --- src/handler/events/eventsHandler.ts | 3 ++ src/handler/events/interactionHandler.ts | 53 ++++++++++++---------- src/handler/events/messageHandler.ts | 58 ++++++++++++------------ src/handler/events/readyHandler.ts | 52 ++++++++++----------- 4 files changed, 85 insertions(+), 81 deletions(-) diff --git a/src/handler/events/eventsHandler.ts b/src/handler/events/eventsHandler.ts index 651cf24a..f1311bf8 100644 --- a/src/handler/events/eventsHandler.ts +++ b/src/handler/events/eventsHandler.ts @@ -4,6 +4,9 @@ import type { EventEmitter } from 'events'; import type SernEmitter from '../sernEmitter'; import type { ErrorHandling, Logging, ModuleManager } from '../contracts'; +/** + * why did i make this, definitely going to be changed in the future + */ export abstract class EventsHandler { protected payloadSubject = new Subject(); protected abstract discordEvent: Observable; diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 8979378d..bade99a4 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -1,20 +1,19 @@ import type { Interaction } from 'discord.js'; -import { catchError, concatMap, from, fromEvent, map, Observable } from 'rxjs'; +import { catchError, concatMap, fromEvent, map, Observable } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import { EventsHandler } from './eventsHandler'; import { SernError } from '../structures/errors'; -import { CommandType, PayloadType } from '../structures/enums'; +import { CommandType } from '../structures/enums'; import { match, P } from 'ts-pattern'; import { - interactionArg, - contextArgs, - commandDispatcher, - dispatchAutocomplete, dispatcher, + contextArgs, interactionArg, + dispatchAutocomplete, dispatchCommand, } from './dispatchers'; -import { executeModule } from './observableHandling'; +import { executeModule, makeModuleExecutor } from './observableHandling'; import type { CommandModule } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; +import SernEmitter from '../sernEmitter'; export default class InteractionHandler extends EventsHandler<{ event: Interaction; @@ -28,10 +27,12 @@ export default class InteractionHandler extends EventsHandler<{ this.payloadSubject .pipe( - map(this.processModules), - concatMap(({ module, execute, controlResult }) => - from(controlResult()).pipe(map(res => ({ module, res, execute }))), //resolve all the Results from event plugins - ), + map(this.createDispatcher), + concatMap( + makeModuleExecutor(module => { + this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + } + )), concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) @@ -42,6 +43,11 @@ export default class InteractionHandler extends EventsHandler<{ const get = (cb: (ms: ModuleStore) => CommandModule | undefined) => { return this.modules.get(cb); }; + /** + * Module retrieval: + * ModuleStores are mapped by Discord API values and modules mapped + * by customId or command name. + */ this.discordEvent.subscribe({ next: event => { if (event.isMessageComponent()) { @@ -50,10 +56,14 @@ export default class InteractionHandler extends EventsHandler<{ ); this.setState({ event, module }); } else if (event.isCommand() || event.isAutocomplete()) { - const module = get( - ms => - ms.ApplicationCommands[event.commandType].get(event.commandName) ?? - ms.BothCommands.get(event.commandName), + const module = get(ms => + /** + * try to fetch from ApplicationCommands, if nothing, try BothCommands + * map. If nothing again,this means a slash command + * exists on the API but not sern + */ + ms.ApplicationCommands[event.commandType].get(event.commandName) ?? + ms.BothCommands.get(event.commandName), ); this.setState({ event, module }); } else if (event.isModalSubmit()) { @@ -64,24 +74,21 @@ export default class InteractionHandler extends EventsHandler<{ } }, error: reason => { - this.emitter.emit('error', { type: PayloadType.Failure, reason }); + this.emitter.emit('error', SernEmitter.failure(undefined, reason)); }, }); } protected setState(state: { event: Interaction; module: CommandModule | undefined }): void { if (state.module === undefined) { - this.emitter.emit('warning', { - type: PayloadType.Warning, - reason: 'Found no module for this interaction', - }); + this.emitter.emit('warning', SernEmitter.warning('Found no module for this interaction')); } else { //if statement above checks already, safe cast this.payloadSubject.next(state as { event: Interaction; module: CommandModule }); } } - protected processModules({ module, event }: { event: Interaction; module: CommandModule }) { + protected createDispatcher({ module, event }: { event: Interaction; module: CommandModule }) { return match(module) .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) //P.union = either CommandType.Slash or CommandType.Both @@ -94,13 +101,13 @@ export default class InteractionHandler extends EventsHandler<{ */ return dispatchAutocomplete(module, event); } else { - return commandDispatcher(module, contextArgs(event)); + return dispatchCommand(module, contextArgs(event)); } }) /** * Every other command module takes a one argument parameter, its corresponding interaction * this makes this usage safe */ - .otherwise((mod) => dispatcher(mod, interactionArg(event))); + .otherwise((mod) => dispatchCommand(mod, interactionArg(event))); } } diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index d8dd691d..6c18bf0e 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -1,37 +1,34 @@ import { EventsHandler } from './eventsHandler'; -import { catchError, concatMap, from, fromEvent, map, Observable, of, switchMap } from 'rxjs'; +import { catchError, concatMap, EMPTY, fromEvent, map, Observable, of } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import type { Message } from 'discord.js'; -import { executeModule, ignoreNonBot, isOneOfCorrectModules } from './observableHandling'; +import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import { fmt } from '../utilities/messageHelpers'; import Context from '../structures/context'; -import { CommandType, PayloadType } from '../structures/enums'; -import { arrAsync } from '../utilities/arrAsync'; -import type { CommandModule, TextCommand } from '../../types/module'; +import type { CommandModule, Module } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; +import { dispatchCommand } from './dispatchers'; +import { _const } from '../utilities/functions'; +import { SernError } from '../structures/errors'; +import SernEmitter from '../sernEmitter'; export default class MessageHandler extends EventsHandler<{ - ctx: Context; - args: ['text', string[]]; - module: TextCommand; + module: Module; + args: unknown[] }> { protected discordEvent: Observable; + public constructor(protected wrapper: Wrapper) { super(wrapper); this.discordEvent = >fromEvent(this.client, 'messageCreate'); this.init(); this.payloadSubject .pipe( - switchMap(({ module, ctx, args }) => { - //refactor to model interaction handler usage - const controlResult = arrAsync( - module.onEvent.map(ep => ep.execute(ctx, args)), - ); - const execute = () => module.execute(ctx, args); - //resolves the promise and re-emits it back into source - return from(controlResult).pipe(map(res => ({ module, execute, res }))); - }), + concatMap( + makeModuleExecutor(module => { + this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + })), concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) @@ -47,28 +44,29 @@ export default class MessageHandler extends EventsHandler<{ this.discordEvent .pipe( ignoreNonBot(this.wrapper.defaultPrefix), - map(message => { + //This concatMap checks if module is undefined, and if it is, do not continue. + // Synonymous to filterMap, but i haven't thought of a generic implementation for filterMap yet + concatMap(message => { const [prefix, ...rest] = fmt(message, defaultPrefix); - return { - ctx: Context.wrap(message), - args: <['text', string[]]>['text', rest], - module: get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)), + const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)); + if(module === undefined) { + return EMPTY; + } + const payload = { + args: [Context.wrap(message), ['text', rest]], //todo: use contextArgs helper function instead + module, }; + return of(payload); }), - concatMap(element => - of(element.module).pipe( - isOneOfCorrectModules(CommandType.Text), - map(module => ({ ...element, module })), - ), - ), + map(({ args, module }) => dispatchCommand(module!, _const(args))), ) .subscribe({ next: value => this.setState(value), - error: reason => this.emitter.emit('error', { type: PayloadType.Failure, reason }), + error: reason => this.emitter.emit('error', SernEmitter.failure(reason)), }); } - protected setState(state: { ctx: Context; args: ['text', string[]]; module: TextCommand }) { + protected setState(state: { module: Module, args: unknown[] }) { this.payloadSubject.next(state); } } diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index 7b86784a..ea6d118a 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -1,9 +1,9 @@ import { EventsHandler } from './eventsHandler'; import type Wrapper from '../structures/wrapper'; -import { concatMap, fromEvent, Observable, map, take } from 'rxjs'; +import { concatMap, fromEvent, type Observable, take } from 'rxjs'; import * as Files from '../utilities/readFile'; -import { defineAllFields$, errTap, processPlugins, resolveInitPlugins$ } from './observableHandling'; -import { CommandType, PayloadType } from '../structures/enums'; +import { errTap, scanModule } from './observableHandling'; +import { CommandType } from '../structures/enums'; import { SernError } from '../structures/errors'; import { match } from 'ts-pattern'; import { Result } from 'ts-results-es'; @@ -13,6 +13,8 @@ import type { DefinedCommandModule, DefinedEventModule } from '../../types/handl import type { ModuleManager } from '../contracts'; import type { ModuleStore } from '../structures/moduleStore'; import { _const, err, ok } from '../utilities/functions'; +import { defineAllFields$ } from './operators'; +import SernEmitter from '../sernEmitter'; export default class ReadyHandler extends EventsHandler<{ module: DefinedCommandModule; @@ -25,42 +27,36 @@ export default class ReadyHandler extends EventsHandler<{ this.discordEvent = ready$.pipe( concatMap(() => Files.buildData(wrapper.commands).pipe( - errTap(reason => - this.emitter.emit('module.register', { - type: PayloadType.Failure, - module: undefined, - reason, - }), - ), - ), + errTap(reason => { + this.emitter.emit('module.register', SernEmitter.failure(undefined, reason)); + })) ), ); this.init(); this.payloadSubject - .pipe(concatMap(processPlugins), resolveInitPlugins$) - .subscribe(({success, module }) => { - if (success) { - const res = registerModule(this.modules, module); - if (res.err) { - this.crashHandler.crash(Error(SernError.InvalidModuleType)); + .pipe( + concatMap( + scanModule({ + onFailure: module => { + this.emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)); + }, + onSuccess: ( {module} ) => { + this.emitter.emit('module.register', SernEmitter.success(module)); + return module; } - this.emitter.emit('module.register', { - type: PayloadType.Success, - module - }); - } else { - this.emitter.emit('module.register', { - type: PayloadType.Failure, - module, - reason: SernError.PluginFailure, - }); + })), + ) + .subscribe(module => { + const res = registerModule(this.modules, module); + if (res.err) { + this.crashHandler.crash(Error(SernError.InvalidModuleType)); } }); } protected init() { this.discordEvent.pipe( - defineAllFields$ + defineAllFields$, ).subscribe({ next: value => this.setState(value), complete: () => this.payloadSubject.unsubscribe(), From 1748fb18fb9d206aad3ad54c991e292fd91b56b3 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:53:06 -0600 Subject: [PATCH 20/59] feat: simplifying sern and docs --- src/handler/events/dispatchers/index.ts | 2 + src/handler/events/dispatchers/provideArgs.ts | 13 ++ src/handler/events/observableHandling.ts | 213 +++++++----------- .../events/userDefinedEventsHandling.ts | 57 ++--- src/handler/sernEmitter.ts | 18 +- src/handler/utilities/functions.ts | 19 +- src/types/module.ts | 2 +- 7 files changed, 141 insertions(+), 183 deletions(-) create mode 100644 src/handler/events/dispatchers/index.ts create mode 100644 src/handler/events/dispatchers/provideArgs.ts diff --git a/src/handler/events/dispatchers/index.ts b/src/handler/events/dispatchers/index.ts new file mode 100644 index 00000000..f0514a7c --- /dev/null +++ b/src/handler/events/dispatchers/index.ts @@ -0,0 +1,2 @@ +export * from './dispatchers'; +export * from './provideArgs'; \ No newline at end of file diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts new file mode 100644 index 00000000..59ae3e45 --- /dev/null +++ b/src/handler/events/dispatchers/provideArgs.ts @@ -0,0 +1,13 @@ +import type { ChatInputCommandInteraction, Interaction, Message } from 'discord.js'; +import Context from '../../structures/context'; +import type { SlashOptions } from '../../../types/handler'; + +export function contextArgs(i: Interaction | Message) { + const ctx = Context.wrap(i as ChatInputCommandInteraction | Message); + const args = ['slash', ctx.interaction.options]; + return () => [ctx, args] as [Context, ['slash', SlashOptions]]; +} + +export function interactionArg(interaction: T) { + return () => [interaction] as [T]; +} diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index e5c56a82..c4555553 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,34 +1,28 @@ -import type { Message } from 'discord.js'; -import { concatMap, from, map, Observable, of, switchMap, tap, throwError } from 'rxjs'; -import { SernError } from '../structures/errors'; +import type { Awaitable, Message } from 'discord.js'; +import { concatMap, EMPTY, from, Observable, of, tap, throwError } from 'rxjs'; +import type { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; -import type { CommandType } from '../structures/enums'; -import { PayloadType } from '../structures/enums'; -import type { AnyModule, CommandModule, CommandModuleDefs, Module } from '../../types/module'; -import { _const, isEmpty, nameOrFilename } from '../utilities/functions'; -import type SernEmitter from '../sernEmitter'; +import type { AnyModule, Module } from '../../types/module'; +import { _const as i } from '../utilities/functions'; +import SernEmitter from '../sernEmitter'; import type { AnyDefinedModule } from '../../types/handler'; -import { processCommandPlugins } from './userDefinedEventsHandling'; -import type { PluginResult } from '../plugins'; +import { callPlugin$, everyPluginOk$, filterMapTo$ } from './operators'; -export function ignoreNonBot(prefix: string) { - return (src: Observable) => - new Observable(subscriber => { - return src.subscribe({ - next(m) { - const messageFromHumanAndHasPrefix = - !m.author.bot && - m.content - .slice(0, prefix.length) - .localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0; - if (messageFromHumanAndHasPrefix) { - subscriber.next(m); - } - }, - error: e => subscriber.error(e), - complete: () => subscriber.complete(), - }); - }); +export function ignoreNonBot(prefix: string) { + return (src: Observable) => new Observable(subscriber => { + return src.subscribe({ + next(m) { + const messageFromHumanAndHasPrefix = + !m.author.bot && + m.content + .slice(0, prefix.length) + .localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0; + if (messageFromHumanAndHasPrefix) { + subscriber.next(m); + } + }, + }); + }); } /** @@ -46,129 +40,88 @@ export function errTap(cb: (err: SernError) => void) { subscriber.next(value.val); } }, - error: e => subscriber.error(e), - complete: () => subscriber.complete(), - }); - }); -} - -//POG -export function isOneOfCorrectModules(...inputs: [...T]) { - return (src: Observable) => { - return new Observable(subscriber => { - return src.subscribe({ - next(mod) { - if (mod === undefined) { - return throwError(_const(SernError.UndefinedModule)); - } - if (inputs.some(type => (mod.type & type) !== 0)) { - subscriber.next(mod as CommandModuleDefs[T[number]]); - } else { - return throwError(_const(SernError.MismatchModule)); - } - }, - error: e => subscriber.error(e), - complete: () => subscriber.complete(), }); }); - }; } export function executeModule( emitter: SernEmitter, - payload: { + { module, task }: { module: Module; - execute: () => unknown; - res: Result[]; + task: () => Awaitable; }, ) { - if (payload.res.every(el => el.ok)) { - const executeFn = Result.wrapAsync(() => - Promise.resolve(payload.execute()), - ); - return from(executeFn).pipe( - concatMap(res => { - if (res.err) { - return throwError(() => ({ - type: PayloadType.Failure, - reason: res.val, - module: payload.module, - })); + return of(module).pipe( + concatMap(() => Result.wrapAsync(async () => task())), + concatMap(result => { + if (result.ok) { + emitter.emit('module.activate', SernEmitter.success(module)); + return EMPTY; + } else { + return throwError(i(SernEmitter.failure(module, result.val))); + } + }), + ); +} + +/** + * A higher order function that + * - executes all plugins { config.createStream } + * - any failures results to { config.onFailure } being called + * - if all plugins are ok, the stream is converted to { config.onSuccess } + * emit config.onSuccess Observable + * @param config + * @returns receiver function for flattening a stream of data + */ +export function createPluginResolver< + T extends Module, + Args extends { module: T; [key: string]: unknown }, + Output>( + config: { + onFailure?: (module: T) => unknown; + onSuccess: (args: Args) => Output, + createStream: (args: Args) => Observable>; + }) { + return (args: Args) => { + const task = config.createStream(args); + return task.pipe( + tap(result => { + if (result.err) { + config.onFailure?.(args.module); } - return of(res.val).pipe( - tap(() => - emitter.emit('module.activate', { - type: PayloadType.Success, - module: payload.module as AnyModule, - }), - ), - ); }), + everyPluginOk$, + filterMapTo$(() => config.onSuccess(args)), ); - } else { - emitter.emit('module.activate', { - type: PayloadType.Failure, - module: payload.module as AnyModule, - reason: SernError.PluginFailure, - }); - return of(undefined); - } + }; } /** - * Plugins are successful if all results are ok. - * Reduces initResult into a single boolean - * @param src + * Calls a module's init plugins and checks for Err. If so, call { onFailure } and + * ignore the module */ -export function resolveInitPlugins$( - src: Observable<{ module: T, initResult: PluginResult[], }>, -) { - return src.pipe( - concatMap(({ module, initResult }) => { - if (isEmpty(initResult)) - return of({ module, success: true }); - else - return from(Promise.all(initResult)).pipe( - reduceResults$, - map(success => ({ module, success })), - ); - }), - ); -} - -export function processPlugins(payload: { - module: T; - absPath: string; +export function scanModule( + config : { + onFailure?: (module: T) => unknown, + onSuccess :(module: Args) => T }) { - const initResult = processCommandPlugins(payload); - return of({ module: payload.module, initResult }); + return createPluginResolver({ + createStream: (args) => from(args.module.plugins).pipe(callPlugin$(args)), + ...config + }); } /** - * fills the defaults for modules - * signature : Observable<{ absPath: string; module: CommandModule | EventModule }> -> Observable<{ absPath: string; module: Processed }> + * Creates an executable task ( execute the command ) if all control plugins are successful + * @param onFailure emits a failure response to the SernEmitter */ -export function defineAllFields$( - src: Observable<{ absPath: string; module: T }>, +export function makeModuleExecutor( + onFailure: (m: M) => unknown, ) { - const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ - absPath, - module: { - name: nameOrFilename(module.name, absPath), - description: module.description ?? '...', - ...module, - }, + const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); + return createPluginResolver({ + onFailure, + createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin$(args)), + onSuccess, }); - return src.pipe( - map(fillFields), - ); -} - -/** - * Reduces a stream of results into a single boolean value - * possible refactor in future to lazily check? - * @param src - */ -export function reduceResults$(src: Observable[]>): Observable { - return src.pipe(switchMap(s => of(s.every(a => a.ok)))); } \ No newline at end of file diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 0007be1e..5f0e80c6 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,27 +1,19 @@ -import { catchError, concatMap, filter, iif, map, of, tap } from 'rxjs'; +import { catchError, concatMap, map, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; import type { AnyDefinedModule, Dependencies } from '../../types/handler'; -import { EventType, PayloadType } from '../structures/enums'; +import { EventType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; -import { defineAllFields$, errTap, processPlugins, reduceResults$, resolveInitPlugins$ } from './observableHandling'; -import type { AnyModule, EventModule } from '../../types/module'; +import { errTap, scanModule } from './observableHandling'; +import type { EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; -import type SernEmitter from '../sernEmitter'; +import SernEmitter from '../sernEmitter'; import { match } from 'ts-pattern'; import type { ErrorHandling, Logging } from '../contracts'; import { SernError } from '../structures/errors'; import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; +import { defineAllFields$ } from './operators'; -/** - * Utility function to process command plugins for all Modules - * @param payload - */ -export function processCommandPlugins( - payload: { module: T; absPath: string; } -) { - return payload.module.plugins.map(plug => plug.execute(payload)); -} export function processEvents({ containerConfig, events }: Wrapper) { const [client, errorHandling, sernEmitter, logging] = containerConfig.get( @@ -32,24 +24,16 @@ export function processEvents({ containerConfig, events }: Wrapper) { ) as [EventEmitter, ErrorHandling, SernEmitter, Logging?]; const lazy = (k: string) => containerConfig.get(k as keyof Dependencies)[0]; const eventStream$ = eventObservable$(events!, sernEmitter); - const emitSuccess$ = (module: AnyModule) => - of({ type: PayloadType.Failure, module, reason: SernError.PluginFailure }).pipe( - tap(it => sernEmitter.emit('module.register', it)), - ); - const emitFailure$ = (module: AnyModule) => - of({ type: PayloadType.Success, module } as const).pipe( - tap(it => sernEmitter.emit('module.register', it)), - ); + const eventCreation$ = eventStream$.pipe( defineAllFields$, - concatMap(processPlugins), - resolveInitPlugins$, - concatMap(({ success, module }) => - iif(() => success, emitFailure$(module), emitSuccess$(module)).pipe( - filter(res => res.type === PayloadType.Success), - map(() => module), - ), - ), + concatMap( scanModule({ + onFailure: module => sernEmitter.emit('module.register',SernEmitter.success(module)), + onSuccess: ( { module }) => { + sernEmitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)); + return module; + } + })), ); const intoDispatcher = (e: AnyDefinedModule) => match(e) .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) @@ -59,6 +43,9 @@ export function processEvents({ containerConfig, events }: Wrapper) { eventCreation$.pipe( map(intoDispatcher), + /** + * Where all events are turned on + */ tap(dispatcher => dispatcher.subscribe()), catchError(handleError(errorHandling, logging)), ).subscribe(); @@ -66,12 +53,8 @@ export function processEvents({ containerConfig, events }: Wrapper) { function eventObservable$(events: string, emitter: SernEmitter) { return buildData(events).pipe( - errTap(reason => - emitter.emit('module.register', { - type: PayloadType.Failure, - module: undefined, - reason, - }), - ), + errTap(reason => { + emitter.emit('module.register', SernEmitter.failure(undefined, reason)); + }), ); } diff --git a/src/handler/sernEmitter.ts b/src/handler/sernEmitter.ts index e643ceb3..7ecacc23 100644 --- a/src/handler/sernEmitter.ts +++ b/src/handler/sernEmitter.ts @@ -1,5 +1,7 @@ import { EventEmitter } from 'events'; -import type { SernEventsMapping } from '../types/handler'; +import type { Payload, SernEventsMapping } from '../types/handler'; +import { PayloadType } from './structures/enums'; +import type { Module } from '../types/module'; class SernEmitter extends EventEmitter { /** @@ -35,6 +37,20 @@ class SernEmitter extends EventEmitter { ): boolean { return super.emit(eventName, ...args); } + private static payload(type: PayloadType, module?: Module, reason?: unknown) { + return { type, module, reason } as T; + } + static failure(module?: Module, reason?: unknown) { + //The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload + // same goes to the other static methods + return SernEmitter.payload(PayloadType.Failure, module, reason); + } + static success(module: Module) { + return SernEmitter.payload(PayloadType.Success, module); + } + static warning(reason: unknown) { + return SernEmitter.payload(PayloadType.Warning, undefined, reason); + } } export default SernEmitter; diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts index 64c6cbb7..cfd4cbb9 100644 --- a/src/handler/utilities/functions.ts +++ b/src/handler/utilities/functions.ts @@ -1,25 +1,20 @@ import * as Files from './readFile'; import { basename } from 'path'; -import { Err, Ok, Result } from 'ts-results-es'; -import { Observable, of, switchMap } from 'rxjs'; +import { Err, Ok } from 'ts-results-es'; /** * A function that returns whatever value is provided. * Used for singleton in iti * @param value */ -export const _const = - (value: T) => - () => - value; +// prettier-ignore +export const _const = (value: T) => () => value; /** * A function that returns another function * Used for transient in iti * @param value */ -export const transient = - (value: T) => - () => - _const(value); +// prettier-ignore +export const transient = (value: T) => () => _const(value); export function nameOrFilename(modName: string | undefined, absPath: string) { return modName ?? Files.fmtFileName(basename(absPath)); @@ -41,7 +36,3 @@ export function partition(arr: (T & V)[], condition: (e: T & V) => boolean } return [t, v]; } - -export function isEmpty(array: unknown[]) : boolean { - return array.length === 0; -} diff --git a/src/types/module.ts b/src/types/module.ts index bbff1708..d2f3f916 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -38,7 +38,7 @@ export interface Module { onEvent: ControlPlugin[]; plugins: InitPlugin[]; description?: string; - execute: (...args: any[]) => any; + execute: (...args: any[]) => Awaitable; } export interface TextCommand extends Module { From ab4bdaabfe744627dd11a6ee12f8165d34a38fb6 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 10 Jan 2023 20:12:23 -0600 Subject: [PATCH 21/59] fix: typings --- src/handler/plugins/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 708b4839..2920f272 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -51,4 +51,4 @@ export type InputEvent = { export type InputCommand = { [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] }; -}[EventType]; \ No newline at end of file +}[CommandType]; \ No newline at end of file From 724e9ae09c9df34c04525c872b2d0bcf41e090e4 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 11:35:07 -0600 Subject: [PATCH 22/59] docs: clarity of function name --- src/handler/events/messageHandler.ts | 5 ++--- src/handler/events/observableHandling.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index 6c18bf0e..540b2816 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -25,9 +25,8 @@ export default class MessageHandler extends EventsHandler<{ this.init(); this.payloadSubject .pipe( - concatMap( - makeModuleExecutor(module => { - this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + concatMap( makeModuleExecutor(module => { + this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); })), concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index c4555553..1c76b9df 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -28,6 +28,7 @@ export function ignoreNonBot(prefix: string) { /** * If the current value in Result stream is an error, calls callback. * @param cb + * @returns Observable<{ module: T; absPath: string }> */ export function errTap(cb: (err: SernError) => void) { return (src: Observable>) => @@ -66,14 +67,14 @@ export function executeModule( /** * A higher order function that - * - executes all plugins { config.createStream } + * - creates a stream of Result { config.createStream } * - any failures results to { config.onFailure } being called - * - if all plugins are ok, the stream is converted to { config.onSuccess } + * - if all results are ok, the stream is converted to { config.onSuccess } * emit config.onSuccess Observable * @param config * @returns receiver function for flattening a stream of data */ -export function createPluginResolver< +export function createResultResolver< T extends Module, Args extends { module: T; [key: string]: unknown }, Output>( @@ -105,7 +106,7 @@ export function scanModule unknown, onSuccess :(module: Args) => T }) { - return createPluginResolver({ + return createResultResolver({ createStream: (args) => from(args.module.plugins).pipe(callPlugin$(args)), ...config }); @@ -119,7 +120,7 @@ export function makeModuleExecutor unknown, ) { const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); - return createPluginResolver({ + return createResultResolver({ onFailure, createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin$(args)), onSuccess, From 4939ac54f6826daaf6e42ef80038a4bd7c702704 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 11:52:37 -0600 Subject: [PATCH 23/59] docs: add documentation for executeModule --- src/handler/events/observableHandling.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 1c76b9df..996c3850 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -8,6 +8,10 @@ import SernEmitter from '../sernEmitter'; import type { AnyDefinedModule } from '../../types/handler'; import { callPlugin$, everyPluginOk$, filterMapTo$ } from './operators'; +/** + * Ignores messages from any person / bot except itself + * @param prefix + */ export function ignoreNonBot(prefix: string) { return (src: Observable) => new Observable(subscriber => { return src.subscribe({ @@ -27,6 +31,7 @@ export function ignoreNonBot(prefix: string) { /** * If the current value in Result stream is an error, calls callback. + * This also extracts the Ok value from Result * @param cb * @returns Observable<{ module: T; absPath: string }> */ @@ -45,6 +50,14 @@ export function errTap(cb: (err: SernError) => void) { }); } +/** + * Wraps the task in a Result as a try / catch. + * if the task is ok, an event is emitted and the stream becomes empty + * if the task is an error, throw an error down the stream which will be handled by catchError + * @param emitter reference to SernEmitter that will emit a successful execution of module + * @param module the module that will be executed with task + * @param task the deferred execution which will be called + */ export function executeModule( emitter: SernEmitter, { module, task }: { @@ -53,6 +66,7 @@ export function executeModule( }, ) { return of(module).pipe( + //converting the task into a promise so rxjs can resolve the Awaitable properly concatMap(() => Result.wrapAsync(async () => task())), concatMap(result => { if (result.ok) { From c2c7ba9bfd29f7953b9e5a0cb9eff85b63a6656b Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 12:16:22 -0600 Subject: [PATCH 24/59] feat: contextArgs overloads --- src/handler/events/dispatchers/dispatchers.ts | 4 ++-- src/handler/events/dispatchers/provideArgs.ts | 12 ++++++----- src/handler/events/messageHandler.ts | 10 ++++------ src/handler/events/operators/index.ts | 3 +-- src/handler/sernEmitter.ts | 2 +- test/cjs/src/index.ts | 0 test/cjs/tsconfig.json | 20 ------------------- test/esm/src/index.ts | 0 test/esm/tsconfig.json | 20 ------------------- 9 files changed, 15 insertions(+), 56 deletions(-) delete mode 100644 test/cjs/src/index.ts delete mode 100644 test/cjs/tsconfig.json delete mode 100644 test/esm/src/index.ts delete mode 100644 test/esm/tsconfig.json diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index ff57d0c1..50542405 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -7,9 +7,9 @@ import treeSearch from '../../utilities/treeSearch'; import type { BothCommand, Module, SlashCommand } from '../../../types/module'; import { EventEmitter } from 'events'; import * as assert from 'assert'; -import { createPluginResolver } from '../observableHandling'; import { concatMap, from, fromEvent, map, Observable } from 'rxjs'; import { callPlugin$ } from '../operators'; +import { createResultResolver } from '../observableHandling'; export function dispatchCommand( module: Module, @@ -41,7 +41,7 @@ export function eventDispatcher( map(event => Array.isArray(event) ? event : [event]), map( args => ({ module, args })) ); - const createResult$ = createPluginResolver({ + const createResult$ = createResultResolver({ onSuccess: ({ args } ) => args, createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin$(args)), }); diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts index 59ae3e45..15803167 100644 --- a/src/handler/events/dispatchers/provideArgs.ts +++ b/src/handler/events/dispatchers/provideArgs.ts @@ -1,11 +1,13 @@ import type { ChatInputCommandInteraction, Interaction, Message } from 'discord.js'; import Context from '../../structures/context'; -import type { SlashOptions } from '../../../types/handler'; +import type { Args, SlashOptions } from '../../../types/handler'; -export function contextArgs(i: Interaction | Message) { - const ctx = Context.wrap(i as ChatInputCommandInteraction | Message); - const args = ['slash', ctx.interaction.options]; - return () => [ctx, args] as [Context, ['slash', SlashOptions]]; +export function contextArgs(wrap: Message, messageArgs?: string[]) : () => [Context, ['text', string[]]]; +export function contextArgs(wrap: Interaction) : () => [Context, ['slash', SlashOptions]]; +export function contextArgs(wrap: Interaction | Message, messageArgs?: string[]) { + const ctx = Context.wrap(wrap as ChatInputCommandInteraction | Message); + const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.interaction.options]; + return () => [ctx, args] as [Context, Args]; } export function interactionArg(interaction: T) { diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index 540b2816..9eab0628 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -4,12 +4,10 @@ import type Wrapper from '../structures/wrapper'; import type { Message } from 'discord.js'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import { fmt } from '../utilities/messageHelpers'; -import Context from '../structures/context'; import type { CommandModule, Module } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; -import { dispatchCommand } from './dispatchers'; -import { _const } from '../utilities/functions'; +import { contextArgs, dispatchCommand } from './dispatchers'; import { SernError } from '../structures/errors'; import SernEmitter from '../sernEmitter'; @@ -44,7 +42,7 @@ export default class MessageHandler extends EventsHandler<{ .pipe( ignoreNonBot(this.wrapper.defaultPrefix), //This concatMap checks if module is undefined, and if it is, do not continue. - // Synonymous to filterMap, but i haven't thought of a generic implementation for filterMap yet + // Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet concatMap(message => { const [prefix, ...rest] = fmt(message, defaultPrefix); const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)); @@ -52,12 +50,12 @@ export default class MessageHandler extends EventsHandler<{ return EMPTY; } const payload = { - args: [Context.wrap(message), ['text', rest]], //todo: use contextArgs helper function instead + args: contextArgs(message, rest), module, }; return of(payload); }), - map(({ args, module }) => dispatchCommand(module!, _const(args))), + map(({ args, module }) => dispatchCommand(module, args)), ) .subscribe({ next: value => this.setState(value), diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index d4047a5c..eb9b03f2 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -31,8 +31,7 @@ export function callPlugin$(args: V) { } /** - * fills the defaults for modules - * signature : Observable<{ absPath: string; module: CommandModule | EventModule }> -> Observable<{ absPath: string; module: Processed }> + * operator function that fill the defaults for a module, */ export function defineAllFields$( src: Observable<{ absPath: string; module: T }>, diff --git a/src/handler/sernEmitter.ts b/src/handler/sernEmitter.ts index 7ecacc23..c2c3d219 100644 --- a/src/handler/sernEmitter.ts +++ b/src/handler/sernEmitter.ts @@ -42,7 +42,7 @@ class SernEmitter extends EventEmitter { } static failure(module?: Module, reason?: unknown) { //The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload - // same goes to the other static methods + // same goes to the other methods below return SernEmitter.payload(PayloadType.Failure, module, reason); } static success(module: Module) { diff --git a/test/cjs/src/index.ts b/test/cjs/src/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/test/cjs/tsconfig.json b/test/cjs/tsconfig.json deleted file mode 100644 index 2c9a9490..00000000 --- a/test/cjs/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "commonjs", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "noImplicitAny": true, - "strictNullChecks": true, - "importsNotUsedAsValues": "error", - "baseUrl": ".", - "resolveJsonModule": true, - "moduleResolution": "node", - "skipLibCheck": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/secrets.json", "src"] -} diff --git a/test/esm/src/index.ts b/test/esm/src/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/test/esm/tsconfig.json b/test/esm/tsconfig.json deleted file mode 100644 index aa5da382..00000000 --- a/test/esm/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "noImplicitAny": true, - "strictNullChecks": true, - "importsNotUsedAsValues": "error", - "baseUrl": ".", - "resolveJsonModule": true, - "moduleResolution": "node", - "skipLibCheck": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/secrets.json", "src"] -} From 600bc809f85557c741f4a479d711dac9a6b793a0 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 15:44:12 -0600 Subject: [PATCH 25/59] docs: found out why --- src/handler/dependencies/provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/handler/dependencies/provider.ts b/src/handler/dependencies/provider.ts index ae438b77..90256e2b 100644 --- a/src/handler/dependencies/provider.ts +++ b/src/handler/dependencies/provider.ts @@ -76,7 +76,6 @@ export function composeRoot( export function useContainer() { const container = containerSubject.getValue()! as unknown as Container; assert.ok(container !== null, 'useContainer was called before Sern#init'); - //weird edge case, why can i not use _const here? return (...keys: [...V]) => keys.map(key => Result.wrap(() => container.get(key)).unwrapOr(undefined)) as MapDeps; } From 9d7760a6513b19a1dcaed613e3f13798f2858622 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 17:52:07 -0600 Subject: [PATCH 26/59] fix: typings --- src/handler/events/dispatchers/provideArgs.ts | 5 +++++ src/handler/events/operators/index.ts | 4 ++-- src/handler/plugins/args.ts | 6 +++--- src/handler/plugins/plugin.ts | 5 +++-- src/handler/sern.ts | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts index 15803167..27b719a2 100644 --- a/src/handler/events/dispatchers/provideArgs.ts +++ b/src/handler/events/dispatchers/provideArgs.ts @@ -2,6 +2,11 @@ import type { ChatInputCommandInteraction, Interaction, Message } from 'discord. import Context from '../../structures/context'; import type { Args, SlashOptions } from '../../../types/handler'; +/** + * function overloads to create an arguments list for Context + * @param wrap + * @param messageArgs + */ export function contextArgs(wrap: Message, messageArgs?: string[]) : () => [Context, ['text', string[]]]; export function contextArgs(wrap: Interaction) : () => [Context, ['slash', SlashOptions]]; export function contextArgs(wrap: Interaction | Message, messageArgs?: string[]) { diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index eb9b03f2..0365dcec 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -1,8 +1,8 @@ import { concatMap, defaultIfEmpty, EMPTY, every, map, Observable, of } from 'rxjs'; -import type { ControlPlugin, InitPlugin } from '../../plugins'; import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; import type { Result } from 'ts-results-es'; +import type { Awaitable } from 'discord.js'; /** * if {src} is true, mapTo V, else ignore @@ -21,7 +21,7 @@ export function filterMapTo$(item: () => V) { * @param args if an array, its spread and plugin called. */ export function callPlugin$(args: V) { - return (src: Observable) => + return (src: Observable<{ execute: (...args: any[]) => Awaitable}>) => src.pipe(concatMap(async plugin => { if (Array.isArray(args)) { return plugin.execute(...args); diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index 5812ae92..7dcf685f 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -76,7 +76,7 @@ type CommandArgsMatrix = { }; [CommandType.Modal]: { [PluginType.Control] : [/* library coupled */ModalSubmitInteraction] - [PluginType.Init] : [ModalSubmitCommand] + [PluginType.Init] : [InitArgs>] }; }; @@ -87,11 +87,11 @@ type EventArgsMatrix = { }; [EventType.Sern] : { [PluginType.Control] : [Payload] - [PluginType.Init] : [[InitArgs>]] + [PluginType.Init] : [InitArgs>] }; [EventType.External] : { [PluginType.Control] : [unknown[]] - [PluginType.Init] : [ExternalEventCommand] + [PluginType.Init] : [InitArgs>] } } diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 2920f272..246809db 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -18,6 +18,7 @@ import type { CommandModuleDefs, EventModuleDefs } from '../../types/module'; import type { EventType, PluginType } from '../structures/enums'; import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +import type { InitArgs } from './args'; export type PluginResult = Awaitable>; export interface Plugin { @@ -42,8 +43,8 @@ export type EventModulesNoPlugins = { [T in EventType]: Omit; }; -export type AnyCommandPlugin = ControlPlugin | InitPlugin<[DefinedCommandModule]>; -export type AnyEventPlugin = ControlPlugin | InitPlugin<[DefinedEventModule]>; +export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs]>; +export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs]>; export type InputEvent = { [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; diff --git a/src/handler/sern.ts b/src/handler/sern.ts index d739b2d6..2999847a 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -2,6 +2,7 @@ import type Wrapper from './structures/wrapper'; import { processEvents } from './events/userDefinedEventsHandling'; import { CommandType, EventType, PluginType } from './structures/enums'; import type { + AnyEventPlugin, ControlPlugin, InitPlugin, InputCommand, InputEvent, Plugin, @@ -15,6 +16,7 @@ import type { Dependencies, OptionalDependencies } from '../types/handler'; import { composeRoot, containerSubject, useContainer } from './dependencies/provider'; import type { Logging } from './contracts'; import { err, ok, partition } from './utilities/functions'; +import type { Awaitable, ClientEvents } from 'discord.js'; /** * * @param wrapper Options to pass into sern. @@ -83,6 +85,21 @@ export function eventModule(mod: InputEvent): EventModule { plugins, } as EventModule; } + +/** + * Create event modules from discord.js client events, + * This is an {@link eventModule} for discord events, + * where typings can be very bad. + * @param mod + */ +export function discordEvent(mod: { + name: T; + type: EventType.Discord; + plugins: AnyEventPlugin[], + execute: (...args: ClientEvents[T]) => Awaitable +}) { + return eventModule(mod); +} /** * @param conf a configuration for creating your project dependencies */ From 46b991850a02eacea7dd437a4ef9679ca6d41cf0 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 18:33:57 -0600 Subject: [PATCH 27/59] feat: shorten operators signature --- src/handler/events/observableHandling.ts | 2 +- src/handler/events/operators/index.ts | 38 ++++++++++++++---------- src/types/handler.ts | 2 +- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 996c3850..abe84bc2 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -105,7 +105,7 @@ export function createResultResolver< config.onFailure?.(args.module); } }), - everyPluginOk$, + everyPluginOk$(), filterMapTo$(() => config.onSuccess(args)), ); }; diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index 0365dcec..bee2eb9c 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -1,4 +1,13 @@ -import { concatMap, defaultIfEmpty, EMPTY, every, map, Observable, of } from 'rxjs'; +import { + concatMap, + defaultIfEmpty, + EMPTY, + every, + map, + Observable, + of, OperatorFunction, + pipe, +} from 'rxjs'; import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; import type { Result } from 'ts-results-es'; @@ -8,26 +17,23 @@ import type { Awaitable } from 'discord.js'; * if {src} is true, mapTo V, else ignore * @param item */ -export function filterMapTo$(item: () => V) { - return (src: Observable) => src.pipe( - concatMap( shouldKeep => +export function filterMapTo$(item: () => V): OperatorFunction { + return pipe(concatMap( shouldKeep => shouldKeep ? of(item()) : EMPTY - ) - ); + )); } /** * Calls any plugin with {args}. * @param args if an array, its spread and plugin called. */ -export function callPlugin$(args: V) { - return (src: Observable<{ execute: (...args: any[]) => Awaitable}>) => - src.pipe(concatMap(async plugin => { - if (Array.isArray(args)) { - return plugin.execute(...args); - } - return plugin.execute(args); - })); +export function callPlugin$(args: V): OperatorFunction<{ execute : (...args: any[]) => Awaitable}, any> { + return pipe(concatMap(async plugin => { + if (Array.isArray(args)) { + return plugin.execute(...args); + } + return plugin.execute(args); + })); } /** @@ -53,8 +59,8 @@ export function defineAllFields$( * Checks if the stream of results is all ok. * @param src */ -export function everyPluginOk$(src: Observable>) { - return src.pipe( +export function everyPluginOk$() : OperatorFunction, boolean> { + return pipe( every(result => result.ok), defaultIfEmpty(true), ); diff --git a/src/types/handler.ts b/src/types/handler.ts index 6c1e0682..b6992fc5 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -1,4 +1,4 @@ -import type { CommandInteractionOptionResolver } from 'discord.js'; +import type { Awaitable, CommandInteractionOptionResolver } from 'discord.js'; import type { PayloadType } from '../handler/structures/enums'; import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { EventEmitter } from 'events'; From 2c437718bffc6759591f3e1a522b50231665ef47 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 18:48:58 -0600 Subject: [PATCH 28/59] refactor: switch to correct convention --- src/handler/events/dispatchers/dispatchers.ts | 18 +++++++++--------- src/handler/events/observableHandling.ts | 10 +++++----- src/handler/events/operators/index.ts | 8 ++++---- src/handler/events/readyHandler.ts | 4 ++-- .../events/userDefinedEventsHandling.ts | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index 50542405..d248ae2e 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -7,8 +7,8 @@ import treeSearch from '../../utilities/treeSearch'; import type { BothCommand, Module, SlashCommand } from '../../../types/module'; import { EventEmitter } from 'events'; import * as assert from 'assert'; -import { concatMap, from, fromEvent, map, Observable } from 'rxjs'; -import { callPlugin$ } from '../operators'; +import { concatMap, from, fromEvent, map, Observable, OperatorFunction, pipe } from 'rxjs'; +import { callPlugin } from '../operators'; import { createResultResolver } from '../observableHandling'; export function dispatchCommand( @@ -37,22 +37,22 @@ export function eventDispatcher( * operator function flattens events into an array * @param src */ - const arrayifySource$ = (src: Observable) => src.pipe( + const arrayify = pipe( map(event => Array.isArray(event) ? event : [event]), map( args => ({ module, args })) ); - const createResult$ = createResultResolver({ + const createResult = createResultResolver({ onSuccess: ({ args } ) => args, - createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin$(args)), + createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin(args)), }); - const execute$ = (src: Observable) => src.pipe( + const execute: OperatorFunction = pipe( concatMap(async args => module.execute(...args)) ); return fromEvent(source, module.name) .pipe( - arrayifySource$, - concatMap(createResult$), - execute$ + arrayify, + concatMap(createResult), + execute ); } diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index abe84bc2..46fe7965 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -6,7 +6,7 @@ import type { AnyModule, Module } from '../../types/module'; import { _const as i } from '../utilities/functions'; import SernEmitter from '../sernEmitter'; import type { AnyDefinedModule } from '../../types/handler'; -import { callPlugin$, everyPluginOk$, filterMapTo$ } from './operators'; +import { callPlugin, everyPluginOk, filterMapTo } from './operators'; /** * Ignores messages from any person / bot except itself @@ -105,8 +105,8 @@ export function createResultResolver< config.onFailure?.(args.module); } }), - everyPluginOk$(), - filterMapTo$(() => config.onSuccess(args)), + everyPluginOk(), + filterMapTo(() => config.onSuccess(args)), ); }; } @@ -121,7 +121,7 @@ export function scanModule T }) { return createResultResolver({ - createStream: (args) => from(args.module.plugins).pipe(callPlugin$(args)), + createStream: (args) => from(args.module.plugins).pipe(callPlugin(args)), ...config }); } @@ -136,7 +136,7 @@ export function makeModuleExecutor ({ task: () => module.execute(...args), module }); return createResultResolver({ onFailure, - createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin$(args)), + createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), onSuccess, }); } \ No newline at end of file diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index bee2eb9c..afe67666 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -17,7 +17,7 @@ import type { Awaitable } from 'discord.js'; * if {src} is true, mapTo V, else ignore * @param item */ -export function filterMapTo$(item: () => V): OperatorFunction { +export function filterMapTo(item: () => V): OperatorFunction { return pipe(concatMap( shouldKeep => shouldKeep ? of(item()) : EMPTY )); @@ -27,7 +27,7 @@ export function filterMapTo$(item: () => V): OperatorFunction { * Calls any plugin with {args}. * @param args if an array, its spread and plugin called. */ -export function callPlugin$(args: V): OperatorFunction<{ execute : (...args: any[]) => Awaitable}, any> { +export function callPlugin(args: V): OperatorFunction<{ execute : (...args: any[]) => Awaitable}, any> { return pipe(concatMap(async plugin => { if (Array.isArray(args)) { return plugin.execute(...args); @@ -39,7 +39,7 @@ export function callPlugin$(args: V): OperatorFunction<{ execute : (...args: /** * operator function that fill the defaults for a module, */ -export function defineAllFields$( +export function defineAllFields( src: Observable<{ absPath: string; module: T }>, ) { const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ @@ -59,7 +59,7 @@ export function defineAllFields$( * Checks if the stream of results is all ok. * @param src */ -export function everyPluginOk$() : OperatorFunction, boolean> { +export function everyPluginOk() : OperatorFunction, boolean> { return pipe( every(result => result.ok), defaultIfEmpty(true), diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index ea6d118a..707356dc 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -13,7 +13,7 @@ import type { DefinedCommandModule, DefinedEventModule } from '../../types/handl import type { ModuleManager } from '../contracts'; import type { ModuleStore } from '../structures/moduleStore'; import { _const, err, ok } from '../utilities/functions'; -import { defineAllFields$ } from './operators'; +import { defineAllFields } from './operators'; import SernEmitter from '../sernEmitter'; export default class ReadyHandler extends EventsHandler<{ @@ -56,7 +56,7 @@ export default class ReadyHandler extends EventsHandler<{ protected init() { this.discordEvent.pipe( - defineAllFields$, + defineAllFields, ).subscribe({ next: value => this.setState(value), complete: () => this.payloadSubject.unsubscribe(), diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 5f0e80c6..29731de6 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -12,7 +12,7 @@ import type { ErrorHandling, Logging } from '../contracts'; import { SernError } from '../structures/errors'; import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; -import { defineAllFields$ } from './operators'; +import { defineAllFields } from './operators'; export function processEvents({ containerConfig, events }: Wrapper) { @@ -26,7 +26,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { const eventStream$ = eventObservable$(events!, sernEmitter); const eventCreation$ = eventStream$.pipe( - defineAllFields$, + defineAllFields, concatMap( scanModule({ onFailure: module => sernEmitter.emit('module.register',SernEmitter.success(module)), onSuccess: ( { module }) => { From 01ee9340cbb6eb9033e56082cc7af93d1d8475d9 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:25:42 -0600 Subject: [PATCH 29/59] refactor: take(1) -> first() --- src/handler/events/readyHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index 707356dc..0bab80a1 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -1,6 +1,6 @@ import { EventsHandler } from './eventsHandler'; import type Wrapper from '../structures/wrapper'; -import { concatMap, fromEvent, type Observable, take } from 'rxjs'; +import { concatMap, first, fromEvent, type Observable, take } from 'rxjs'; import * as Files from '../utilities/readFile'; import { errTap, scanModule } from './observableHandling'; import { CommandType } from '../structures/enums'; @@ -23,7 +23,7 @@ export default class ReadyHandler extends EventsHandler<{ protected discordEvent!: Observable<{ module: CommandModule; absPath: string }>; constructor(wrapper: Wrapper) { super(wrapper); - const ready$ = fromEvent(this.client, 'ready').pipe(take(1)); + const ready$ = fromEvent(this.client, 'ready').pipe(first()); this.discordEvent = ready$.pipe( concatMap(() => Files.buildData(wrapper.commands).pipe( From 9418bc67b24b5ee7e2db8ef94900f310e8b4d982 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:26:55 -0600 Subject: [PATCH 30/59] refactor: revert --- src/handler/events/readyHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index 0bab80a1..707356dc 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -1,6 +1,6 @@ import { EventsHandler } from './eventsHandler'; import type Wrapper from '../structures/wrapper'; -import { concatMap, first, fromEvent, type Observable, take } from 'rxjs'; +import { concatMap, fromEvent, type Observable, take } from 'rxjs'; import * as Files from '../utilities/readFile'; import { errTap, scanModule } from './observableHandling'; import { CommandType } from '../structures/enums'; @@ -23,7 +23,7 @@ export default class ReadyHandler extends EventsHandler<{ protected discordEvent!: Observable<{ module: CommandModule; absPath: string }>; constructor(wrapper: Wrapper) { super(wrapper); - const ready$ = fromEvent(this.client, 'ready').pipe(first()); + const ready$ = fromEvent(this.client, 'ready').pipe(take(1)); this.discordEvent = ready$.pipe( concatMap(() => Files.buildData(wrapper.commands).pipe( From aeee6ba8366a9ae42491dd0dfb44c0eed0ea9d0a Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:01:51 -0600 Subject: [PATCH 31/59] refactor: safer typings (less any) and more accurate typings --- src/handler/contracts/moduleManager.ts | 5 ++-- src/handler/events/dispatchers/dispatchers.ts | 20 +++++++-------- src/handler/events/interactionHandler.ts | 9 ++++--- src/handler/events/messageHandler.ts | 11 ++++---- src/handler/events/observableHandling.ts | 12 ++++----- src/handler/events/operators/index.ts | 17 ++++++------- src/handler/events/readyHandler.ts | 16 ++++++------ .../events/userDefinedEventsHandling.ts | 8 +++--- src/handler/plugins/args.ts | 6 ++--- src/handler/plugins/plugin.ts | 14 +++++------ src/handler/sern.ts | 13 +++------- src/handler/structures/events.ts | 4 +-- src/handler/structures/moduleStore.ts | 25 ++++++++++--------- src/index.ts | 6 ++--- src/types/handler.ts | 6 ++--- src/types/module.ts | 8 ++---- 16 files changed, 86 insertions(+), 94 deletions(-) diff --git a/src/handler/contracts/moduleManager.ts b/src/handler/contracts/moduleManager.ts index dc47abdd..64c202bc 100644 --- a/src/handler/contracts/moduleManager.ts +++ b/src/handler/contracts/moduleManager.ts @@ -1,17 +1,18 @@ import type { CommandModuleDefs } from '../../types/module'; import type { CommandType } from '../structures/enums'; import type { ModuleStore } from '../structures/moduleStore'; +import type { Processed } from '../../types/handler'; export interface ModuleManager { get( - strat: (ms: ModuleStore) => CommandModuleDefs[T] | undefined, + strat: (ms: ModuleStore) => Processed | undefined, ): CommandModuleDefs[T] | undefined; set(strat: (ms: ModuleStore) => void): void; } export class DefaultModuleManager implements ModuleManager { constructor(private moduleStore: ModuleStore) {} - get(strat: (ms: ModuleStore) => CommandModuleDefs[T] | undefined) { + get(strat: (ms: ModuleStore) => Processed | undefined) { return strat(this.moduleStore); } diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index d248ae2e..d6d1714a 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -1,18 +1,18 @@ -import type { DefinedEventModule } from '../../../types/handler'; +import type { Processed } from '../../../types/handler'; import type { AutocompleteInteraction, } from 'discord.js'; import { SernError } from '../../structures/errors'; import treeSearch from '../../utilities/treeSearch'; -import type { BothCommand, Module, SlashCommand } from '../../../types/module'; +import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../types/module'; import { EventEmitter } from 'events'; import * as assert from 'assert'; -import { concatMap, from, fromEvent, map, Observable, OperatorFunction, pipe } from 'rxjs'; +import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; import { callPlugin } from '../operators'; import { createResultResolver } from '../observableHandling'; export function dispatchCommand( - module: Module, + module: Processed, createArgs: () => unknown[], ) { const args = createArgs(); @@ -28,7 +28,7 @@ export function dispatchCommand( * @param source */ export function eventDispatcher( - module: DefinedEventModule, + module: Processed, source: unknown, ) { assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); @@ -38,14 +38,14 @@ export function eventDispatcher( * @param src */ const arrayify = pipe( - map(event => Array.isArray(event) ? event : [event]), + map(event => Array.isArray(event) ? event as unknown[] : [event]), map( args => ({ module, args })) ); - const createResult = createResultResolver({ - onSuccess: ({ args } ) => args, + const createResult = createResultResolver, { module: Processed; args: unknown[] }, unknown[]>({ createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin(args)), + onSuccess: ({ args } ) => args }); - const execute: OperatorFunction = pipe( + const execute: OperatorFunction = pipe( concatMap(async args => module.execute(...args)) ); return fromEvent(source, module.name) @@ -56,7 +56,7 @@ export function eventDispatcher( ); } -export function dispatchAutocomplete(module: BothCommand | SlashCommand, interaction: AutocompleteInteraction) { +export function dispatchAutocomplete(module: Processed, interaction: AutocompleteInteraction) { const option = treeSearch(interaction, module.options); if (option !== undefined) { return { diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index bade99a4..d98fbcec 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -14,10 +14,11 @@ import type { CommandModule } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; import SernEmitter from '../sernEmitter'; +import type { Processed } from '../../types/handler'; export default class InteractionHandler extends EventsHandler<{ event: Interaction; - module: CommandModule; + module: Processed; }> { protected override discordEvent: Observable; constructor(wrapper: Wrapper) { @@ -40,7 +41,7 @@ export default class InteractionHandler extends EventsHandler<{ } override init() { - const get = (cb: (ms: ModuleStore) => CommandModule | undefined) => { + const get = (cb: (ms: ModuleStore) => Processed | undefined) => { return this.modules.get(cb); }; /** @@ -84,11 +85,11 @@ export default class InteractionHandler extends EventsHandler<{ this.emitter.emit('warning', SernEmitter.warning('Found no module for this interaction')); } else { //if statement above checks already, safe cast - this.payloadSubject.next(state as { event: Interaction; module: CommandModule }); + this.payloadSubject.next(state as { event: Interaction; module: Processed }); } } - protected createDispatcher({ module, event }: { event: Interaction; module: CommandModule }) { + protected createDispatcher({ module, event }: { event: Interaction; module: Processed }) { return match(module) .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) //P.union = either CommandType.Slash or CommandType.Both diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index 9eab0628..0f32c191 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -4,15 +4,16 @@ import type Wrapper from '../structures/wrapper'; import type { Message } from 'discord.js'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import { fmt } from '../utilities/messageHelpers'; -import type { CommandModule, Module } from '../../types/module'; +import type { CommandModule, Module, TextCommand } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; import { contextArgs, dispatchCommand } from './dispatchers'; import { SernError } from '../structures/errors'; import SernEmitter from '../sernEmitter'; +import type { Processed } from '../../types/handler'; export default class MessageHandler extends EventsHandler<{ - module: Module; + module: Processed; args: unknown[] }> { protected discordEvent: Observable; @@ -35,7 +36,7 @@ export default class MessageHandler extends EventsHandler<{ protected init(): void { if (this.wrapper.defaultPrefix === undefined) return; //for now, just ignore if prefix doesn't exist const { defaultPrefix } = this.wrapper; - const get = (cb: (ms: ModuleStore) => CommandModule | undefined) => { + const get = (cb: (ms: ModuleStore) => Processed | undefined) => { return this.modules.get(cb); }; this.discordEvent @@ -55,7 +56,7 @@ export default class MessageHandler extends EventsHandler<{ }; return of(payload); }), - map(({ args, module }) => dispatchCommand(module, args)), + map(({ args, module }) => dispatchCommand(module as Processed, args)), ) .subscribe({ next: value => this.setState(value), @@ -63,7 +64,7 @@ export default class MessageHandler extends EventsHandler<{ }); } - protected setState(state: { module: Module, args: unknown[] }) { + protected setState(state: { module: Processed, args: unknown[] }) { this.payloadSubject.next(state); } } diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index 46fe7965..e1323902 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -2,11 +2,11 @@ import type { Awaitable, Message } from 'discord.js'; import { concatMap, EMPTY, from, Observable, of, tap, throwError } from 'rxjs'; import type { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; -import type { AnyModule, Module } from '../../types/module'; +import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module'; import { _const as i } from '../utilities/functions'; import SernEmitter from '../sernEmitter'; -import type { AnyDefinedModule } from '../../types/handler'; import { callPlugin, everyPluginOk, filterMapTo } from './operators'; +import type { Processed } from '../../types/handler'; /** * Ignores messages from any person / bot except itself @@ -61,7 +61,7 @@ export function errTap(cb: (err: SernError) => void) { export function executeModule( emitter: SernEmitter, { module, task }: { - module: Module; + module: Processed; task: () => Awaitable; }, ) { @@ -89,7 +89,7 @@ export function executeModule( * @returns receiver function for flattening a stream of data */ export function createResultResolver< - T extends Module, + T extends Processed, Args extends { module: T; [key: string]: unknown }, Output>( config: { @@ -115,7 +115,7 @@ export function createResultResolver< * Calls a module's init plugins and checks for Err. If so, call { onFailure } and * ignore the module */ -export function scanModule( +export function scanModule, Args extends { module: T, absPath: string }>( config : { onFailure?: (module: T) => unknown, onSuccess :(module: Args) => T @@ -130,7 +130,7 @@ export function scanModule( +export function makeModuleExecutor, Args extends { module: M; args: unknown[] }>( onFailure: (m: M) => unknown, ) { const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index afe67666..b2d20ac2 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -4,14 +4,13 @@ import { EMPTY, every, map, - Observable, of, OperatorFunction, pipe, } from 'rxjs'; import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; import type { Result } from 'ts-results-es'; -import type { Awaitable } from 'discord.js'; +import type { PluginResult } from '../../plugins'; /** * if {src} is true, mapTo V, else ignore @@ -27,7 +26,10 @@ export function filterMapTo(item: () => V): OperatorFunction { * Calls any plugin with {args}. * @param args if an array, its spread and plugin called. */ -export function callPlugin(args: V): OperatorFunction<{ execute : (...args: any[]) => Awaitable}, any> { +export function callPlugin(args: V): OperatorFunction<{ + execute : (...args: unknown[]) => PluginResult }, + Result + > { return pipe(concatMap(async plugin => { if (Array.isArray(args)) { return plugin.execute(...args); @@ -37,11 +39,9 @@ export function callPlugin(args: V): OperatorFunction<{ execute : (...args: a } /** - * operator function that fill the defaults for a module, + * operator function that fill the defaults for a module */ -export function defineAllFields( - src: Observable<{ absPath: string; module: T }>, -) { +export function defineAllFields() { const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({ absPath, module: { @@ -50,14 +50,13 @@ export function defineAllFields( ...module, } }); - return src.pipe( + return pipe( map(fillFields), ); } /** * Checks if the stream of results is all ok. - * @param src */ export function everyPluginOk() : OperatorFunction, boolean> { return pipe( diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index 707356dc..e033eabc 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -9,7 +9,7 @@ import { match } from 'ts-pattern'; import { Result } from 'ts-results-es'; import { ApplicationCommandType, ComponentType } from 'discord.js'; import type { CommandModule } from '../../types/module'; -import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; +import type { Processed } from '../../types/handler'; import type { ModuleManager } from '../contracts'; import type { ModuleStore } from '../structures/moduleStore'; import { _const, err, ok } from '../utilities/functions'; @@ -17,7 +17,7 @@ import { defineAllFields } from './operators'; import SernEmitter from '../sernEmitter'; export default class ReadyHandler extends EventsHandler<{ - module: DefinedCommandModule; + module: Processed; absPath: string; }> { protected discordEvent!: Observable<{ module: CommandModule; absPath: string }>; @@ -47,7 +47,7 @@ export default class ReadyHandler extends EventsHandler<{ })), ) .subscribe(module => { - const res = registerModule(this.modules, module); + const res = registerModule(this.modules, module as Processed); if (res.err) { this.crashHandler.crash(Error(SernError.InvalidModuleType)); } @@ -56,27 +56,27 @@ export default class ReadyHandler extends EventsHandler<{ protected init() { this.discordEvent.pipe( - defineAllFields, + defineAllFields(), ).subscribe({ next: value => this.setState(value), complete: () => this.payloadSubject.unsubscribe(), }); } - protected setState(state: { absPath: string; module: DefinedCommandModule }): void { + protected setState(state: { absPath: string; module: Processed }): void { this.payloadSubject.next(state); } } -function registerModule( +function registerModule>( manager: ModuleManager, - mod: DefinedCommandModule | DefinedEventModule, + mod: T, ): Result { const name = mod.name; const insert = (cb: (ms: ModuleStore) => void) => { const set = Result.wrap(_const(manager.set(cb))); return set.ok ? ok() : err(); }; - return match(mod) + return match(mod as Processed) .with({ type: CommandType.Text }, mod => { mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); return insert(ms => ms.TextCommands.set(name, mod)); diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 29731de6..21c408a8 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,10 +1,10 @@ import { catchError, concatMap, map, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; -import type { AnyDefinedModule, Dependencies } from '../../types/handler'; +import type { Dependencies, Processed } from '../../types/handler'; import { EventType } from '../structures/enums'; import type Wrapper from '../structures/wrapper'; import { errTap, scanModule } from './observableHandling'; -import type { EventModule } from '../../types/module'; +import type { CommandModule, EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; import SernEmitter from '../sernEmitter'; import { match } from 'ts-pattern'; @@ -26,7 +26,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { const eventStream$ = eventObservable$(events!, sernEmitter); const eventCreation$ = eventStream$.pipe( - defineAllFields, + defineAllFields(), concatMap( scanModule({ onFailure: module => sernEmitter.emit('module.register',SernEmitter.success(module)), onSuccess: ( { module }) => { @@ -35,7 +35,7 @@ export function processEvents({ containerConfig, events }: Wrapper) { } })), ); - const intoDispatcher = (e: AnyDefinedModule) => match(e) + const intoDispatcher = (e: Processed) => match(e) .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index 7dcf685f..04e6c1c0 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -82,7 +82,7 @@ type CommandArgsMatrix = { type EventArgsMatrix = { [EventType.Discord] : { - [PluginType.Control] : [/* library coupled */ClientEvents[keyof ClientEvents]] + [PluginType.Control] : /* library coupled */ClientEvents[keyof ClientEvents] [PluginType.Init] : [InitArgs>] }; [EventType.Sern] : { @@ -90,7 +90,7 @@ type EventArgsMatrix = { [PluginType.Init] : [InitArgs>] }; [EventType.External] : { - [PluginType.Control] : [unknown[]] + [PluginType.Control] : unknown[] [PluginType.Init] : [InitArgs>] } } @@ -101,4 +101,4 @@ export interface InitArgs { } export type CommandArgs = CommandArgsMatrix[I][J] -export type EventArgs = EventArgsMatrix[I][J] \ No newline at end of file +export type EventArgs = EventArgsMatrix[I][J] \ No newline at end of file diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 246809db..765862c5 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -14,24 +14,24 @@ import type { Awaitable } from 'discord.js'; import type { Result} from 'ts-results-es'; import type { CommandType } from '../structures/enums'; -import type { CommandModuleDefs, EventModuleDefs } from '../../types/module'; +import type { CommandModule, CommandModuleDefs, EventModule, EventModuleDefs } from '../../types/module'; import type { EventType, PluginType } from '../structures/enums'; -import type { DefinedCommandModule, DefinedEventModule } from '../../types/handler'; import type { InitArgs } from './args'; +import type { Processed } from '../../types/handler'; export type PluginResult = Awaitable>; -export interface Plugin { +export interface Plugin { type: PluginType; execute: (...args: Args) => PluginResult } -export interface InitPlugin extends Plugin { +export interface InitPlugin { type: PluginType.Init; execute: (...args: Args) => PluginResult } -export interface ControlPlugin extends Plugin { +export interface ControlPlugin { type: PluginType.Control; execute: (...args: Args) => PluginResult } @@ -43,8 +43,8 @@ export type EventModulesNoPlugins = { [T in EventType]: Omit; }; -export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs]>; -export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs]>; +export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; +export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type InputEvent = { [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 2999847a..437311d2 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -1,12 +1,7 @@ import type Wrapper from './structures/wrapper'; import { processEvents } from './events/userDefinedEventsHandling'; import { CommandType, EventType, PluginType } from './structures/enums'; -import type { - AnyEventPlugin, - ControlPlugin, - InitPlugin, InputCommand, InputEvent, - Plugin, -} from './plugins/plugin'; +import type { AnyEventPlugin, ControlPlugin, InitPlugin, InputCommand, InputEvent, Plugin } from './plugins/plugin'; import InteractionHandler from './events/interactionHandler'; import ReadyHandler from './events/readyHandler'; import MessageHandler from './events/messageHandler'; @@ -17,6 +12,7 @@ import { composeRoot, containerSubject, useContainer } from './dependencies/prov import type { Logging } from './contracts'; import { err, ok, partition } from './utilities/functions'; import type { Awaitable, ClientEvents } from 'discord.js'; + /** * * @param wrapper Options to pass into sern. @@ -94,11 +90,10 @@ export function eventModule(mod: InputEvent): EventModule { */ export function discordEvent(mod: { name: T; - type: EventType.Discord; - plugins: AnyEventPlugin[], + plugins?: AnyEventPlugin[], execute: (...args: ClientEvents[T]) => Awaitable }) { - return eventModule(mod); + return eventModule({ type: EventType.Discord, ...mod }); } /** * @param conf a configuration for creating your project dependencies diff --git a/src/handler/structures/events.ts b/src/handler/structures/events.ts index faaaa09a..43ac309f 100644 --- a/src/handler/structures/events.ts +++ b/src/handler/structures/events.ts @@ -1,4 +1,4 @@ -import type { Payload, SernEventsMapping } from '../../types/handler'; +import type { SernEventsMapping } from '../../types/handler'; import type { Awaitable, ClientEvents } from 'discord.js'; import type { EventType } from './enums'; import type { Module } from '../../types/module'; @@ -7,7 +7,7 @@ export interface SernEventCommand; + execute(...args: SernEventsMapping[T]): Awaitable; } export interface DiscordEventCommand diff --git a/src/handler/structures/moduleStore.ts b/src/handler/structures/moduleStore.ts index 0446dcf7..29a89ad4 100644 --- a/src/handler/structures/moduleStore.ts +++ b/src/handler/structures/moduleStore.ts @@ -1,25 +1,26 @@ import type { CommandModule } from '../../types/module'; import { ApplicationCommandType, ComponentType } from 'discord.js'; +import type { Processed } from '../../types/handler'; /** * Storing all command modules * This dependency is usually injected into ModuleManager */ export class ModuleStore { - readonly BothCommands = new Map(); + readonly BothCommands = new Map>(); readonly ApplicationCommands = { - [ApplicationCommandType.User]: new Map(), - [ApplicationCommandType.Message]: new Map(), - [ApplicationCommandType.ChatInput]: new Map(), + [ApplicationCommandType.User]: new Map>(), + [ApplicationCommandType.Message]: new Map>(), + [ApplicationCommandType.ChatInput]: new Map>(), }; - readonly ModalSubmit = new Map(); - readonly TextCommands = new Map(); + readonly ModalSubmit = new Map>(); + readonly TextCommands = new Map>(); readonly InteractionHandlers = { - [ComponentType.Button]: new Map(), - [ComponentType.StringSelect]: new Map(), - [ComponentType.ChannelSelect]: new Map(), - [ComponentType.MentionableSelect]: new Map(), - [ComponentType.RoleSelect]: new Map(), - [ComponentType.UserSelect]: new Map(), + [ComponentType.Button]: new Map>(), + [ComponentType.StringSelect]: new Map>(), + [ComponentType.ChannelSelect]: new Map>(), + [ComponentType.MentionableSelect]: new Map>(), + [ComponentType.RoleSelect]: new Map>(), + [ComponentType.UserSelect]: new Map>(), }; } diff --git a/src/index.ts b/src/index.ts index f4aed37c..b3d83e17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ import SernEmitter from './handler/sernEmitter'; -export { eventModule, commandModule, EventExecutable, CommandExecutable, controller } from './handler/sern'; +export { eventModule, commandModule, EventExecutable, CommandExecutable, controller, discordEvent } from './handler/sern'; export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; export * from './handler/structures/structxports'; -export * from './handler/plugins/index'; -export * from './handler/contracts/index'; +export * from './handler/plugins'; +export * from './handler/contracts'; export { SernEmitter }; export { _const as single, transient as many } from './handler/utilities/functions'; export { useContainerRaw } from './handler/dependencies/provider'; diff --git a/src/types/handler.ts b/src/types/handler.ts index b6992fc5..bc37d323 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -1,4 +1,4 @@ -import type { Awaitable, CommandInteractionOptionResolver } from 'discord.js'; +import type { CommandInteractionOptionResolver } from 'discord.js'; import type { PayloadType } from '../handler/structures/enums'; import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { EventEmitter } from 'events'; @@ -20,9 +20,7 @@ export type SlashOptions = Omit; export type Payload = | { type: PayloadType.Success; module: AnyModule } | { type: PayloadType.Failure; module?: AnyModule; reason: string | Error } diff --git a/src/types/module.ts b/src/types/module.ts index d2f3f916..17b4ec87 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -33,7 +33,7 @@ import { EventType } from '../handler/structures/enums'; import type { UserSelectMenuInteraction } from 'discord.js'; export interface Module { - type?: CommandType | EventType; + type: CommandType | EventType; name?: string; onEvent: ControlPlugin[]; plugins: InitPlugin[]; @@ -107,11 +107,7 @@ export interface ModalSubmitCommand extends Module { execute: (ctx: ModalSubmitInteraction) => Awaitable; } -export interface AutocompleteCommand extends Module { - name?: never; - description?: never; - type?: never; - plugins: never; +export interface AutocompleteCommand extends Omit { onEvent: ControlPlugin[]; execute: (ctx: AutocompleteInteraction) => Awaitable; } From 53aee3bcf2d36c95877b3a5ed30476a00e7501bb Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:49:40 -0600 Subject: [PATCH 32/59] style: prettier and short type aliases --- src/handler/contracts/moduleManager.ts | 4 +- src/handler/events/dispatchers/dispatchers.ts | 42 +++---- src/handler/events/dispatchers/index.ts | 2 +- src/handler/events/dispatchers/provideArgs.ts | 7 +- src/handler/events/interactionHandler.ts | 94 +++++++++------- src/handler/events/messageHandler.ts | 19 ++-- src/handler/events/observableHandling.ts | 104 +++++++++++------- src/handler/events/operators/index.ts | 51 ++++----- src/handler/events/readyHandler.ts | 26 +++-- .../events/userDefinedEventsHandling.ts | 45 ++++---- src/handler/plugins/args.ts | 100 +++++++++-------- src/handler/plugins/createPlugin.ts | 21 ++-- src/handler/plugins/index.ts | 2 +- src/handler/plugins/plugin.ts | 55 --------- src/handler/sern.ts | 20 +++- src/handler/sernEmitter.ts | 23 +++- src/index.ts | 9 +- src/types/handler.ts | 2 +- src/types/module.ts | 25 ++++- src/types/plugin.ts | 43 ++++++++ 20 files changed, 395 insertions(+), 299 deletions(-) delete mode 100644 src/handler/plugins/plugin.ts create mode 100644 src/types/plugin.ts diff --git a/src/handler/contracts/moduleManager.ts b/src/handler/contracts/moduleManager.ts index 64c202bc..5ba2086f 100644 --- a/src/handler/contracts/moduleManager.ts +++ b/src/handler/contracts/moduleManager.ts @@ -12,7 +12,9 @@ export interface ModuleManager { export class DefaultModuleManager implements ModuleManager { constructor(private moduleStore: ModuleStore) {} - get(strat: (ms: ModuleStore) => Processed | undefined) { + get( + strat: (ms: ModuleStore) => Processed | undefined, + ) { return strat(this.moduleStore); } diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index d6d1714a..5fd12e0c 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -1,7 +1,5 @@ import type { Processed } from '../../../types/handler'; -import type { - AutocompleteInteraction, -} from 'discord.js'; +import type { AutocompleteInteraction } from 'discord.js'; import { SernError } from '../../structures/errors'; import treeSearch from '../../utilities/treeSearch'; import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../types/module'; @@ -11,10 +9,7 @@ import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; import { callPlugin } from '../operators'; import { createResultResolver } from '../observableHandling'; -export function dispatchCommand( - module: Processed, - createArgs: () => unknown[], -) { +export function dispatchCommand(module: Processed, createArgs: () => unknown[]) { const args = createArgs(); return { module, @@ -27,10 +22,7 @@ export function dispatchCommand( * @param module * @param source */ -export function eventDispatcher( - module: Processed, - source: unknown, -) { +export function eventDispatcher(module: Processed, source: unknown) { assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); /** * Sometimes fromEvent emits a single parameter, which is not an Array. This @@ -38,25 +30,27 @@ export function eventDispatcher( * @param src */ const arrayify = pipe( - map(event => Array.isArray(event) ? event as unknown[] : [event]), - map( args => ({ module, args })) + map(event => (Array.isArray(event) ? (event as unknown[]) : [event])), + map(args => ({ module, args })), ); - const createResult = createResultResolver, { module: Processed; args: unknown[] }, unknown[]>({ - createStream: ({ module, args } ) => from(module.onEvent).pipe(callPlugin(args)), - onSuccess: ({ args } ) => args + const createResult = createResultResolver< + Processed, + { module: Processed; args: unknown[] }, + unknown[] + >({ + createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)), + onSuccess: ({ args }) => args, }); const execute: OperatorFunction = pipe( - concatMap(async args => module.execute(...args)) + concatMap(async args => module.execute(...args)), ); - return fromEvent(source, module.name) - .pipe( - arrayify, - concatMap(createResult), - execute - ); + return fromEvent(source, module.name).pipe(arrayify, concatMap(createResult), execute); } -export function dispatchAutocomplete(module: Processed, interaction: AutocompleteInteraction) { +export function dispatchAutocomplete( + module: Processed, + interaction: AutocompleteInteraction, +) { const option = treeSearch(interaction, module.options); if (option !== undefined) { return { diff --git a/src/handler/events/dispatchers/index.ts b/src/handler/events/dispatchers/index.ts index f0514a7c..b754c3e0 100644 --- a/src/handler/events/dispatchers/index.ts +++ b/src/handler/events/dispatchers/index.ts @@ -1,2 +1,2 @@ export * from './dispatchers'; -export * from './provideArgs'; \ No newline at end of file +export * from './provideArgs'; diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts index 27b719a2..21063261 100644 --- a/src/handler/events/dispatchers/provideArgs.ts +++ b/src/handler/events/dispatchers/provideArgs.ts @@ -7,8 +7,11 @@ import type { Args, SlashOptions } from '../../../types/handler'; * @param wrap * @param messageArgs */ -export function contextArgs(wrap: Message, messageArgs?: string[]) : () => [Context, ['text', string[]]]; -export function contextArgs(wrap: Interaction) : () => [Context, ['slash', SlashOptions]]; +export function contextArgs( + wrap: Message, + messageArgs?: string[], +): () => [Context, ['text', string[]]]; +export function contextArgs(wrap: Interaction): () => [Context, ['slash', SlashOptions]]; export function contextArgs(wrap: Interaction | Message, messageArgs?: string[]) { const ctx = Context.wrap(wrap as ChatInputCommandInteraction | Message); const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.interaction.options]; diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index d98fbcec..2c713bf0 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -5,10 +5,7 @@ import { EventsHandler } from './eventsHandler'; import { SernError } from '../structures/errors'; import { CommandType } from '../structures/enums'; import { match, P } from 'ts-pattern'; -import { - contextArgs, interactionArg, - dispatchAutocomplete, dispatchCommand, -} from './dispatchers'; +import { contextArgs, interactionArg, dispatchAutocomplete, dispatchCommand } from './dispatchers'; import { executeModule, makeModuleExecutor } from './observableHandling'; import type { CommandModule } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; @@ -29,11 +26,12 @@ export default class InteractionHandler extends EventsHandler<{ this.payloadSubject .pipe( map(this.createDispatcher), - concatMap( - makeModuleExecutor(module => { - this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); - } - )), + makeModuleExecutor(module => { + this.emitter.emit( + 'module.activate', + SernEmitter.failure(module, SernError.PluginFailure), + ); + }), concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) @@ -57,14 +55,15 @@ export default class InteractionHandler extends EventsHandler<{ ); this.setState({ event, module }); } else if (event.isCommand() || event.isAutocomplete()) { - const module = get(ms => - /** - * try to fetch from ApplicationCommands, if nothing, try BothCommands - * map. If nothing again,this means a slash command - * exists on the API but not sern - */ - ms.ApplicationCommands[event.commandType].get(event.commandName) ?? - ms.BothCommands.get(event.commandName), + const module = get( + ms => + /** + * try to fetch from ApplicationCommands, if nothing, try BothCommands + * map. If nothing again,this means a slash command + * exists on the API but not sern + */ + ms.ApplicationCommands[event.commandType].get(event.commandName) ?? + ms.BothCommands.get(event.commandName), ); this.setState({ event, module }); } else if (event.isModalSubmit()) { @@ -82,33 +81,48 @@ export default class InteractionHandler extends EventsHandler<{ protected setState(state: { event: Interaction; module: CommandModule | undefined }): void { if (state.module === undefined) { - this.emitter.emit('warning', SernEmitter.warning('Found no module for this interaction')); + this.emitter.emit( + 'warning', + SernEmitter.warning('Found no module for this interaction'), + ); } else { //if statement above checks already, safe cast - this.payloadSubject.next(state as { event: Interaction; module: Processed }); + this.payloadSubject.next( + state as { event: Interaction; module: Processed }, + ); } } - protected createDispatcher({ module, event }: { event: Interaction; module: Processed }) { - return match(module) - .with({ type: CommandType.Text }, () => this.crashHandler.crash(Error(SernError.MismatchEvent))) - //P.union = either CommandType.Slash or CommandType.Both - .with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => { - if(event.isAutocomplete()) { - /** - * Autocomplete is a special case that - * must be handled separately, since it's - * too different from regular command modules - */ - return dispatchAutocomplete(module, event); - } else { - return dispatchCommand(module, contextArgs(event)); - } - }) - /** - * Every other command module takes a one argument parameter, its corresponding interaction - * this makes this usage safe - */ - .otherwise((mod) => dispatchCommand(mod, interactionArg(event))); + protected createDispatcher({ + module, + event, + }: { + event: Interaction; + module: Processed; + }) { + return ( + match(module) + .with({ type: CommandType.Text }, () => + this.crashHandler.crash(Error(SernError.MismatchEvent)), + ) + //P.union = either CommandType.Slash or CommandType.Both + .with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => { + if (event.isAutocomplete()) { + /** + * Autocomplete is a special case that + * must be handled separately, since it's + * too different from regular command modules + */ + return dispatchAutocomplete(module, event); + } else { + return dispatchCommand(module, contextArgs(event)); + } + }) + /** + * Every other command module takes a one argument parameter, its corresponding interaction + * this makes this usage safe + */ + .otherwise(mod => dispatchCommand(mod, interactionArg(event))) + ); } } diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index 0f32c191..f24b3fae 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -14,7 +14,7 @@ import type { Processed } from '../../types/handler'; export default class MessageHandler extends EventsHandler<{ module: Processed; - args: unknown[] + args: unknown[]; }> { protected discordEvent: Observable; @@ -24,9 +24,12 @@ export default class MessageHandler extends EventsHandler<{ this.init(); this.payloadSubject .pipe( - concatMap( makeModuleExecutor(module => { - this.emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); - })), + makeModuleExecutor(module => { + this.emitter.emit( + 'module.activate', + SernEmitter.failure(module, SernError.PluginFailure), + ); + }), concatMap(payload => executeModule(this.emitter, payload)), catchError(handleError(this.crashHandler, this.logger)), ) @@ -46,8 +49,10 @@ export default class MessageHandler extends EventsHandler<{ // Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet concatMap(message => { const [prefix, ...rest] = fmt(message, defaultPrefix); - const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)); - if(module === undefined) { + const module = get( + ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix), + ); + if (module === undefined) { return EMPTY; } const payload = { @@ -64,7 +69,7 @@ export default class MessageHandler extends EventsHandler<{ }); } - protected setState(state: { module: Processed, args: unknown[] }) { + protected setState(state: { module: Processed; args: unknown[] }) { this.payloadSubject.next(state); } } diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index e1323902..fe207727 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,5 +1,14 @@ import type { Awaitable, Message } from 'discord.js'; -import { concatMap, EMPTY, from, Observable, of, tap, throwError } from 'rxjs'; +import { + concatMap, + EMPTY, + from, + Observable, + of, + pipe, + tap, + throwError, +} from 'rxjs'; import type { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module'; @@ -7,26 +16,28 @@ import { _const as i } from '../utilities/functions'; import SernEmitter from '../sernEmitter'; import { callPlugin, everyPluginOk, filterMapTo } from './operators'; import type { Processed } from '../../types/handler'; +import type { VoidResult } from '../../types/plugin'; /** * Ignores messages from any person / bot except itself * @param prefix */ export function ignoreNonBot(prefix: string) { - return (src: Observable) => new Observable(subscriber => { - return src.subscribe({ - next(m) { - const messageFromHumanAndHasPrefix = - !m.author.bot && - m.content - .slice(0, prefix.length) - .localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0; - if (messageFromHumanAndHasPrefix) { - subscriber.next(m); - } - }, - }); - }); + return (src: Observable) => + new Observable(subscriber => { + return src.subscribe({ + next(m) { + const messageFromHumanAndHasPrefix = + !m.author.bot && + m.content + .slice(0, prefix.length) + .localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0; + if (messageFromHumanAndHasPrefix) { + subscriber.next(m); + } + }, + }); + }); } /** @@ -60,7 +71,10 @@ export function errTap(cb: (err: SernError) => void) { */ export function executeModule( emitter: SernEmitter, - { module, task }: { + { + module, + task, + }: { module: Processed; task: () => Awaitable; }, @@ -81,7 +95,7 @@ export function executeModule( /** * A higher order function that - * - creates a stream of Result { config.createStream } + * - creates a stream of {@link VoidResult} { config.createStream } * - any failures results to { config.onFailure } being called * - if all results are ok, the stream is converted to { config.onSuccess } * emit config.onSuccess Observable @@ -91,12 +105,12 @@ export function executeModule( export function createResultResolver< T extends Processed, Args extends { module: T; [key: string]: unknown }, - Output>( - config: { - onFailure?: (module: T) => unknown; - onSuccess: (args: Args) => Output, - createStream: (args: Args) => Observable>; - }) { + Output, +>(config: { + onFailure?: (module: T) => unknown; + onSuccess: (args: Args) => Output; + createStream: (args: Args) => Observable; +}) { return (args: Args) => { const task = config.createStream(args); return task.pipe( @@ -115,28 +129,36 @@ export function createResultResolver< * Calls a module's init plugins and checks for Err. If so, call { onFailure } and * ignore the module */ -export function scanModule, Args extends { module: T, absPath: string }>( - config : { - onFailure?: (module: T) => unknown, - onSuccess :(module: Args) => T -}) { - return createResultResolver({ - createStream: (args) => from(args.module.plugins).pipe(callPlugin(args)), - ...config - }); +export function scanModule< + T extends Processed, + Args extends { module: T; absPath: string }, +>(config: { onFailure?: (module: T) => unknown; onSuccess: (module: Args) => T }) { + return pipe( + concatMap( + createResultResolver({ + createStream: args => from(args.module.plugins).pipe(callPlugin(args)), + ...config, + }), + ), + ); } /** * Creates an executable task ( execute the command ) if all control plugins are successful * @param onFailure emits a failure response to the SernEmitter */ -export function makeModuleExecutor, Args extends { module: M; args: unknown[] }>( - onFailure: (m: M) => unknown, -) { +export function makeModuleExecutor< + M extends Processed, + Args extends { module: M; args: unknown[] }, +>(onFailure: (m: M) => unknown) { const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); - return createResultResolver({ - onFailure, - createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), - onSuccess, - }); -} \ No newline at end of file + return pipe( + concatMap( + createResultResolver({ + onFailure, + createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), + onSuccess, + }), + ), + ); +} diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index b2d20ac2..55e474d7 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -1,41 +1,34 @@ -import { - concatMap, - defaultIfEmpty, - EMPTY, - every, - map, - of, OperatorFunction, - pipe, -} from 'rxjs'; +import { concatMap, defaultIfEmpty, EMPTY, every, map, of, OperatorFunction, pipe } from 'rxjs'; import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; -import type { Result } from 'ts-results-es'; -import type { PluginResult } from '../../plugins'; +import type { PluginResult, VoidResult } from '../../../types/plugin'; /** * if {src} is true, mapTo V, else ignore * @param item */ export function filterMapTo(item: () => V): OperatorFunction { - return pipe(concatMap( shouldKeep => - shouldKeep ? of(item()) : EMPTY - )); + return pipe(concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY))); } /** * Calls any plugin with {args}. * @param args if an array, its spread and plugin called. */ -export function callPlugin(args: V): OperatorFunction<{ - execute : (...args: unknown[]) => PluginResult }, - Result - > { - return pipe(concatMap(async plugin => { - if (Array.isArray(args)) { - return plugin.execute(...args); - } - return plugin.execute(args); - })); +export function callPlugin(args: V): OperatorFunction< + { + execute: (...args: unknown[]) => PluginResult; + }, + VoidResult +> { + return pipe( + concatMap(async plugin => { + if (Array.isArray(args)) { + return plugin.execute(...args); + } + return plugin.execute(args); + }), + ); } /** @@ -48,19 +41,17 @@ export function defineAllFields() { name: nameOrFilename(module.name, absPath), description: module.description ?? '...', ...module, - } + }, }); - return pipe( - map(fillFields), - ); + return pipe(map(fillFields)); } /** * Checks if the stream of results is all ok. */ -export function everyPluginOk() : OperatorFunction, boolean> { +export function everyPluginOk(): OperatorFunction { return pipe( every(result => result.ok), defaultIfEmpty(true), ); -} \ No newline at end of file +} diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index e033eabc..512184da 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -28,23 +28,29 @@ export default class ReadyHandler extends EventsHandler<{ concatMap(() => Files.buildData(wrapper.commands).pipe( errTap(reason => { - this.emitter.emit('module.register', SernEmitter.failure(undefined, reason)); - })) + this.emitter.emit( + 'module.register', + SernEmitter.failure(undefined, reason), + ); + }), + ), ), ); this.init(); this.payloadSubject .pipe( - concatMap( - scanModule({ + scanModule({ onFailure: module => { - this.emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)); + this.emitter.emit( + 'module.register', + SernEmitter.failure(module, SernError.PluginFailure), + ); }, - onSuccess: ( {module} ) => { + onSuccess: ({ module }) => { this.emitter.emit('module.register', SernEmitter.success(module)); return module; - } - })), + }, + }), ) .subscribe(module => { const res = registerModule(this.modules, module as Processed); @@ -55,9 +61,7 @@ export default class ReadyHandler extends EventsHandler<{ } protected init() { - this.discordEvent.pipe( - defineAllFields(), - ).subscribe({ + this.discordEvent.pipe(defineAllFields()).subscribe({ next: value => this.setState(value), complete: () => this.payloadSubject.unsubscribe(), }); diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 21c408a8..8b5bb696 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -14,7 +14,6 @@ import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; import { defineAllFields } from './operators'; - export function processEvents({ containerConfig, events }: Wrapper) { const [client, errorHandling, sernEmitter, logging] = containerConfig.get( '@sern/client', @@ -27,28 +26,34 @@ export function processEvents({ containerConfig, events }: Wrapper) { const eventCreation$ = eventStream$.pipe( defineAllFields(), - concatMap( scanModule({ - onFailure: module => sernEmitter.emit('module.register',SernEmitter.success(module)), - onSuccess: ( { module }) => { - sernEmitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)); + scanModule({ + onFailure: module => sernEmitter.emit('module.register', SernEmitter.success(module)), + onSuccess: ({ module }) => { + sernEmitter.emit( + 'module.register', + SernEmitter.failure(module, SernError.PluginFailure), + ); return module; - } - })), + }, + }), ); - const intoDispatcher = (e: Processed) => match(e) - .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) - .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) - .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) - .otherwise(() => errorHandling.crash(Error(SernError.InvalidModuleType))); + const intoDispatcher = (e: Processed) => + match(e) + .with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter)) + .with({ type: EventType.Discord }, m => eventDispatcher(m, client)) + .with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter))) + .otherwise(() => errorHandling.crash(Error(SernError.InvalidModuleType))); - eventCreation$.pipe( - map(intoDispatcher), - /** - * Where all events are turned on - */ - tap(dispatcher => dispatcher.subscribe()), - catchError(handleError(errorHandling, logging)), - ).subscribe(); + eventCreation$ + .pipe( + map(intoDispatcher), + /** + * Where all events are turned on + */ + tap(dispatcher => dispatcher.subscribe()), + catchError(handleError(errorHandling, logging)), + ) + .subscribe(); } function eventObservable$(events: string, emitter: SernEmitter) { diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index 04e6c1c0..e11fa3ba 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -3,11 +3,16 @@ import type { PluginType } from '../structures/enums'; import type { ClientEvents } from 'discord.js'; import type { BothCommand, - ButtonCommand, ChannelSelectCommand, - ContextMenuUser, MentionableSelectCommand, ModalSubmitCommand, + ButtonCommand, + ChannelSelectCommand, + ContextMenuUser, + MentionableSelectCommand, + ModalSubmitCommand, RoleSelectCommand, - SlashCommand, StringSelectCommand, - TextCommand, UserSelectCommand, + SlashCommand, + StringSelectCommand, + TextCommand, + UserSelectCommand, } from '../../types/module'; import type { AnyDefinedModule, Args, Payload, Processed, SlashOptions } from '../../types/handler'; import type Context from '../structures/context'; @@ -26,79 +31,88 @@ import type { UserSelectMenuInteraction, } from 'discord.js'; import { EventType } from '../structures/enums'; -import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from '../structures/events'; - +import type { + DiscordEventCommand, + ExternalEventCommand, + SernEventCommand, +} from '../structures/events'; type CommandArgsMatrix = { [CommandType.Text]: { - [PluginType.Control] : [Context, ['text', string[]]] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [Context, ['text', string[]]]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.Slash]: { - [PluginType.Control] : [Context, ['slash', /* library coupled */ SlashOptions]] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.Both]: { - [PluginType.Control] : [Context, Args] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [Context, Args]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.CtxMsg]: { - [PluginType.Control] : [/* library coupled */MessageContextMenuCommandInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.CtxUser]: { - [PluginType.Control] : [/* library coupled */UserContextMenuCommandInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.Button]: { - [PluginType.Control] : [/* library coupled */ButtonInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ ButtonInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.StringSelect]: { - [PluginType.Control] : [/* library coupled */StringSelectMenuInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.RoleSelect]: { - [PluginType.Control] : [/* library coupled */RoleSelectMenuInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.ChannelSelect]: { - [PluginType.Control] : [/* library coupled */ChannelSelectMenuInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.MentionableSelect]: { - [PluginType.Control] : [/* library coupled */MentionableSelectMenuInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.UserSelect]: { - [PluginType.Control] : [/* library coupled */UserSelectMenuInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction]; + [PluginType.Init]: [InitArgs>]; }; [CommandType.Modal]: { - [PluginType.Control] : [/* library coupled */ModalSubmitInteraction] - [PluginType.Init] : [InitArgs>] + [PluginType.Control]: [/* library coupled */ ModalSubmitInteraction]; + [PluginType.Init]: [InitArgs>]; }; }; type EventArgsMatrix = { - [EventType.Discord] : { - [PluginType.Control] : /* library coupled */ClientEvents[keyof ClientEvents] - [PluginType.Init] : [InitArgs>] + [EventType.Discord]: { + [PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents]; + [PluginType.Init]: [InitArgs>]; }; - [EventType.Sern] : { - [PluginType.Control] : [Payload] - [PluginType.Init] : [InitArgs>] + [EventType.Sern]: { + [PluginType.Control]: [Payload]; + [PluginType.Init]: [InitArgs>]; }; - [EventType.External] : { - [PluginType.Control] : unknown[] - [PluginType.Init] : [InitArgs>] - } -} + [EventType.External]: { + [PluginType.Control]: unknown[]; + [PluginType.Init]: [InitArgs>]; + }; +}; export interface InitArgs { module: T; absPath: string; } -export type CommandArgs = CommandArgsMatrix[I][J] -export type EventArgs = EventArgsMatrix[I][J] \ No newline at end of file +export type CommandArgs< + I extends CommandType = CommandType, + J extends PluginType = PluginType, +> = CommandArgsMatrix[I][J]; +export type EventArgs< + I extends EventType = EventType, + J extends PluginType = PluginType, +> = EventArgsMatrix[I][J]; diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index 9b3ca002..ab09c0b6 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -5,34 +5,34 @@ import type { ClientEvents } from 'discord.js'; export function makePlugin( type: PluginType, - execute: (...args: any[]) => any + execute: (...args: any[]) => any, ): Plugin { return { type, - execute + execute, }; } export function EventInitPlugin( - execute: (...args: EventArgs) => PluginResult + execute: (...args: EventArgs) => PluginResult, ) { return makePlugin(PluginType.Init, execute); } export function CommandInitPlugin( - execute: (...args: CommandArgs) => PluginResult + execute: (...args: CommandArgs) => PluginResult, ) { return makePlugin(PluginType.Init, execute); } export function CommandControlPlugin( - execute: (...args: CommandArgs) => PluginResult -) { + execute: (...args: CommandArgs) => PluginResult, +) { return makePlugin(PluginType.Control, execute); } export function EventControlPlugin( - execute: (...args: EventArgs) => PluginResult + execute: (...args: EventArgs) => PluginResult, ) { return makePlugin(PluginType.Control, execute); } @@ -42,6 +42,9 @@ export function EventControlPlugin( * A specialized function for creating control plugins with discord.js ClientEvents. * Will probably be moved one day! */ -export function DiscordEventControlPlugin(name: T, execute: (...args : ClientEvents[T]) => PluginResult) { +export function DiscordEventControlPlugin( + name: T, + execute: (...args: ClientEvents[T]) => PluginResult, +) { return makePlugin(PluginType.Control, execute); -} \ No newline at end of file +} diff --git a/src/handler/plugins/index.ts b/src/handler/plugins/index.ts index 6ede51b4..c7012226 100644 --- a/src/handler/plugins/index.ts +++ b/src/handler/plugins/index.ts @@ -1,3 +1,3 @@ export { EventArgs, InitArgs, CommandArgs } from './args'; export * from './plugin'; -export * from './createPlugin'; \ No newline at end of file +export * from './createPlugin'; diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts deleted file mode 100644 index 765862c5..00000000 --- a/src/handler/plugins/plugin.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Plugins can be inserted on all commands and are emitted - * - * 1. On ready event, where all commands are loaded. - * 2. On corresponding observable (when command triggers) - * - * The goal of plugins is to organize commands and - * provide extensions to repetitive patterns - * examples include refreshing modules, - * categorizing commands, cool-downs, permissions, etc. - * Plugins are reminiscent of middleware in express. - */ - -import type { Awaitable } from 'discord.js'; -import type { Result} from 'ts-results-es'; -import type { CommandType } from '../structures/enums'; -import type { CommandModule, CommandModuleDefs, EventModule, EventModuleDefs } from '../../types/module'; - -import type { EventType, PluginType } from '../structures/enums'; -import type { InitArgs } from './args'; -import type { Processed } from '../../types/handler'; -export type PluginResult = Awaitable>; - -export interface Plugin { - type: PluginType; - execute: (...args: Args) => PluginResult -} - -export interface InitPlugin { - type: PluginType.Init; - execute: (...args: Args) => PluginResult -} - -export interface ControlPlugin { - type: PluginType.Control; - execute: (...args: Args) => PluginResult -} - -export type CommandModuleNoPlugins = { - [T in CommandType]: Omit; -}; -export type EventModulesNoPlugins = { - [T in EventType]: Omit; -}; - -export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; -export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; - -export type InputEvent = { - [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; -}[EventType]; - -export type InputCommand = { - [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] }; -}[CommandType]; \ No newline at end of file diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 437311d2..51b26cea 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -1,11 +1,23 @@ import type Wrapper from './structures/wrapper'; import { processEvents } from './events/userDefinedEventsHandling'; import { CommandType, EventType, PluginType } from './structures/enums'; -import type { AnyEventPlugin, ControlPlugin, InitPlugin, InputCommand, InputEvent, Plugin } from './plugins/plugin'; +import type { + AnyEventPlugin, + ControlPlugin, + InitPlugin, + InputCommand, + InputEvent, + Plugin, +} from './plugins/plugin'; import InteractionHandler from './events/interactionHandler'; import ReadyHandler from './events/readyHandler'; import MessageHandler from './events/messageHandler'; -import type { CommandModule, CommandModuleDefs, EventModule, EventModuleDefs } from '../types/module'; +import type { + CommandModule, + CommandModuleDefs, + EventModule, + EventModuleDefs, +} from '../types/module'; import { Container, createContainer } from 'iti'; import type { Dependencies, OptionalDependencies } from '../types/handler'; import { composeRoot, containerSubject, useContainer } from './dependencies/provider'; @@ -90,8 +102,8 @@ export function eventModule(mod: InputEvent): EventModule { */ export function discordEvent(mod: { name: T; - plugins?: AnyEventPlugin[], - execute: (...args: ClientEvents[T]) => Awaitable + plugins?: AnyEventPlugin[]; + execute: (...args: ClientEvents[T]) => Awaitable; }) { return eventModule({ type: EventType.Discord, ...mod }); } diff --git a/src/handler/sernEmitter.ts b/src/handler/sernEmitter.ts index c2c3d219..07dc97c0 100644 --- a/src/handler/sernEmitter.ts +++ b/src/handler/sernEmitter.ts @@ -37,19 +37,34 @@ class SernEmitter extends EventEmitter { ): boolean { return super.emit(eventName, ...args); } - private static payload(type: PayloadType, module?: Module, reason?: unknown) { + private static payload( + type: PayloadType, + module?: Module, + reason?: unknown, + ) { return { type, module, reason } as T; } static failure(module?: Module, reason?: unknown) { //The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload // same goes to the other methods below - return SernEmitter.payload(PayloadType.Failure, module, reason); + return SernEmitter.payload( + PayloadType.Failure, + module, + reason, + ); } static success(module: Module) { - return SernEmitter.payload(PayloadType.Success, module); + return SernEmitter.payload( + PayloadType.Success, + module, + ); } static warning(reason: unknown) { - return SernEmitter.payload(PayloadType.Warning, undefined, reason); + return SernEmitter.payload( + PayloadType.Warning, + undefined, + reason, + ); } } diff --git a/src/index.ts b/src/index.ts index b3d83e17..38a595b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,12 @@ import SernEmitter from './handler/sernEmitter'; -export { eventModule, commandModule, EventExecutable, CommandExecutable, controller, discordEvent } from './handler/sern'; +export { + eventModule, + commandModule, + EventExecutable, + CommandExecutable, + controller, + discordEvent, +} from './handler/sern'; export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; diff --git a/src/types/handler.ts b/src/types/handler.ts index bc37d323..9274f4d6 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -48,7 +48,7 @@ export type ReplyOptions = | string | Omit | MessageReplyOptions; - +//prettier-ignore export type MapDeps = T extends [ infer First extends keyof Deps, ...infer Rest extends readonly unknown[], diff --git a/src/types/module.ts b/src/types/module.ts index 17b4ec87..54cb24ea 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -28,9 +28,10 @@ import type { import { CommandType } from '../handler/structures/enums'; import type { Args, SlashOptions } from './handler'; import type Context from '../handler/structures/context'; -import type { InitPlugin, ControlPlugin } from '../handler/plugins'; +import type { InitPlugin, ControlPlugin } from './plugin'; import { EventType } from '../handler/structures/enums'; import type { UserSelectMenuInteraction } from 'discord.js'; +import type { AnyCommandPlugin, AnyEventPlugin } from './plugin'; export interface Module { type: CommandType | EventType; @@ -49,7 +50,7 @@ export interface TextCommand extends Module { export interface SlashCommand extends Module { type: CommandType.Slash; - description: string, + description: string; options?: SernOptionsData[]; execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; } @@ -57,7 +58,7 @@ export interface SlashCommand extends Module { export interface BothCommand extends Module { type: CommandType.Both; alias?: string[]; - description: string, + description: string; options?: SernOptionsData[]; execute: (ctx: Context, args: Args) => Awaitable; } @@ -107,7 +108,8 @@ export interface ModalSubmitCommand extends Module { execute: (ctx: ModalSubmitInteraction) => Awaitable; } -export interface AutocompleteCommand extends Omit { +export interface AutocompleteCommand + extends Omit { onEvent: ControlPlugin[]; execute: (ctx: AutocompleteInteraction) => Awaitable; } @@ -162,6 +164,21 @@ export interface SernAutocompleteData command: AutocompleteCommand; } +export type CommandModuleNoPlugins = { + [T in CommandType]: Omit; +}; +export type EventModulesNoPlugins = { + [T in EventType]: Omit; +}; + +export type InputEvent = { + [T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] }; +}[EventType]; + +export type InputCommand = { + [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] }; +}[CommandType]; + /** * Type that replaces autocomplete with {@link SernAutocompleteData} */ diff --git a/src/types/plugin.ts b/src/types/plugin.ts new file mode 100644 index 00000000..9df00f77 --- /dev/null +++ b/src/types/plugin.ts @@ -0,0 +1,43 @@ +/* + * Plugins can be inserted on all commands and are emitted + * + * 1. On ready event, where all commands are loaded. + * 2. On corresponding observable (when command triggers) + * + * The goal of plugins is to organize commands and + * provide extensions to repetitive patterns + * examples include refreshing modules, + * categorizing commands, cool-downs, permissions, etc. + * Plugins are reminiscent of middleware in express. + */ + +import type { Awaitable } from 'discord.js'; +import type { Result } from 'ts-results-es'; +import type { PluginType } from '../handler/structures/enums'; +import type { + CommandModule, + EventModule, +} from './module'; + +import type { InitArgs } from '../handler/plugins'; +import type { Processed } from './handler'; +export type PluginResult = Awaitable; +export type VoidResult = Result +export interface Plugin { + type: PluginType; + execute: (...args: Args) => PluginResult; +} + +export interface InitPlugin { + type: PluginType.Init; + execute: (...args: Args) => PluginResult; +} + +export interface ControlPlugin { + type: PluginType.Control; + execute: (...args: Args) => PluginResult; +} + + +export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; +export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; From 19bf2d4fd2dec7bcd7c405958768c5f9261e50e1 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:53:24 -0600 Subject: [PATCH 33/59] fix: typings --- src/handler/events/eventsHandler.ts | 2 +- src/handler/events/userDefinedEventsHandling.ts | 2 +- src/handler/plugins/createPlugin.ts | 2 +- src/handler/plugins/index.ts | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/handler/events/eventsHandler.ts b/src/handler/events/eventsHandler.ts index f1311bf8..393f93fd 100644 --- a/src/handler/events/eventsHandler.ts +++ b/src/handler/events/eventsHandler.ts @@ -5,7 +5,7 @@ import type SernEmitter from '../sernEmitter'; import type { ErrorHandling, Logging, ModuleManager } from '../contracts'; /** - * why did i make this, definitely going to be changed in the future + * why did I make this, definitely going to be changed in the future */ export abstract class EventsHandler { protected payloadSubject = new Subject(); diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 8b5bb696..724e419c 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,4 +1,4 @@ -import { catchError, concatMap, map, tap } from 'rxjs'; +import { catchError, map, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; import type { Dependencies, Processed } from '../../types/handler'; import { EventType } from '../structures/enums'; diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index ab09c0b6..9140a344 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -1,5 +1,5 @@ import { CommandType, EventType, PluginType } from '../structures/enums'; -import type { Plugin, PluginResult } from './plugin'; +import type { Plugin, PluginResult } from '../../types/plugin'; import type { CommandArgs, EventArgs } from './args'; import type { ClientEvents } from 'discord.js'; diff --git a/src/handler/plugins/index.ts b/src/handler/plugins/index.ts index c7012226..3f8a6623 100644 --- a/src/handler/plugins/index.ts +++ b/src/handler/plugins/index.ts @@ -1,3 +1,2 @@ export { EventArgs, InitArgs, CommandArgs } from './args'; -export * from './plugin'; export * from './createPlugin'; From 8cd812e6d28d2b514431ba1b47b54f85aad1c460 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:53:51 -0600 Subject: [PATCH 34/59] fix: typings --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 38a595b7..7d9d229d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export { export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; +export * from './types/plugin'; export * from './handler/structures/structxports'; export * from './handler/plugins'; export * from './handler/contracts'; From a552a0fbe8887f50b5618deeafe48c1948ac527d Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:22:47 -0600 Subject: [PATCH 35/59] docs: add deprecations --- src/handler/sern.ts | 6 ++---- src/types/handler.ts | 3 ++- src/types/plugin.ts | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 51b26cea..4dcd2aa7 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -5,10 +5,8 @@ import type { AnyEventPlugin, ControlPlugin, InitPlugin, - InputCommand, - InputEvent, Plugin, -} from './plugins/plugin'; +} from '../types/plugin'; import InteractionHandler from './events/interactionHandler'; import ReadyHandler from './events/readyHandler'; import MessageHandler from './events/messageHandler'; @@ -16,7 +14,7 @@ import type { CommandModule, CommandModuleDefs, EventModule, - EventModuleDefs, + EventModuleDefs, InputCommand, InputEvent } from '../types/module'; import { Container, createContainer } from 'iti'; import type { Dependencies, OptionalDependencies } from '../types/handler'; diff --git a/src/types/handler.ts b/src/types/handler.ts index 9274f4d6..9ee0e27b 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -60,4 +60,5 @@ export type MapDeps = T : [never]; //Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest export type OptionalDependencies = '@sern/logger'; -export type Processed = T & { name: string; description: string }; \ No newline at end of file +export type Processed = T & { name: string; description: string }; +export type Deprecated = [never, Message] \ No newline at end of file diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 9df00f77..797635a2 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -15,14 +15,15 @@ import type { Awaitable } from 'discord.js'; import type { Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; import type { - CommandModule, + CommandModule, CommandModuleDefs, EventModule, } from './module'; import type { InitArgs } from '../handler/plugins'; -import type { Processed } from './handler'; +import type { Deprecated, Processed } from './handler'; export type PluginResult = Awaitable; export type VoidResult = Result + export interface Plugin { type: PluginType; execute: (...args: Args) => PluginResult; @@ -32,7 +33,6 @@ export interface InitPlugin { type: PluginType.Init; execute: (...args: Args) => PluginResult; } - export interface ControlPlugin { type: PluginType.Control; execute: (...args: Args) => PluginResult; @@ -41,3 +41,14 @@ export interface ControlPlugin { export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; + + +export type CommandPlugin = Deprecated<'Please view alternatives: '> +export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '> +export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '> +export type SernEmitterPlugin = Deprecated<'Please view alternatives: '> +export type AutocompletePlugin = Deprecated<'Please view alternatives: '> +export type EventPlugin = Deprecated<'Please view alternatives: '> +export type SernEventPlugin = Deprecated<'Please view alternatives: '> +export type ExternalEventPlugin = Deprecated<'Please view alternatives: '> +export type DiscordEventPlugin = Deprecated<'Please view alternatives: '> From 1352f4534db685440ac676e81d66ac5e2cda2c98 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:42:42 -0600 Subject: [PATCH 36/59] refactor: organization and moving stuff --- src/handler/plugins/args.ts | 11 +++-------- src/handler/structures/events.ts | 25 ------------------------- src/handler/utilities/readFile.ts | 3 ++- src/handler/utilities/treeSearch.ts | 5 +++++ src/types/module.ts | 28 +++++++++++++++++++++++----- 5 files changed, 33 insertions(+), 39 deletions(-) delete mode 100644 src/handler/structures/events.ts diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index e11fa3ba..868c41d4 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -5,19 +5,19 @@ import type { BothCommand, ButtonCommand, ChannelSelectCommand, - ContextMenuUser, + ContextMenuUser, DiscordEventCommand, ExternalEventCommand, MentionableSelectCommand, ModalSubmitCommand, - RoleSelectCommand, + RoleSelectCommand, SernEventCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand, + ContextMenuMsg } from '../../types/module'; import type { AnyDefinedModule, Args, Payload, Processed, SlashOptions } from '../../types/handler'; import type Context from '../structures/context'; import type { MessageContextMenuCommandInteraction } from 'discord.js'; -import type { ContextMenuMsg } from '../../types/module'; import type { ButtonInteraction, RoleSelectMenuInteraction, @@ -31,11 +31,6 @@ import type { UserSelectMenuInteraction, } from 'discord.js'; import { EventType } from '../structures/enums'; -import type { - DiscordEventCommand, - ExternalEventCommand, - SernEventCommand, -} from '../structures/events'; type CommandArgsMatrix = { [CommandType.Text]: { diff --git a/src/handler/structures/events.ts b/src/handler/structures/events.ts deleted file mode 100644 index 43ac309f..00000000 --- a/src/handler/structures/events.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { SernEventsMapping } from '../../types/handler'; -import type { Awaitable, ClientEvents } from 'discord.js'; -import type { EventType } from './enums'; -import type { Module } from '../../types/module'; - -export interface SernEventCommand - extends Module { - name?: T; - type: EventType.Sern; - execute(...args: SernEventsMapping[T]): Awaitable; -} - -export interface DiscordEventCommand - extends Module { - name?: T; - type: EventType.Discord; - execute(...args: ClientEvents[T]): Awaitable; -} - -export interface ExternalEventCommand extends Module { - name?: string; - emitter: string; - type: EventType.External; - execute(...args: unknown[]): Awaitable; -} diff --git a/src/handler/utilities/readFile.ts b/src/handler/utilities/readFile.ts index 28f56cee..3df7a656 100644 --- a/src/handler/utilities/readFile.ts +++ b/src/handler/utilities/readFile.ts @@ -22,7 +22,8 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] { export const fmtFileName = (n: string) => n.substring(0, n.length - 3); /** - * + * a directory string is converted into a stream of modules. + * starts the stream of modules that sern needs to process on init * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files * @param commandDir */ diff --git a/src/handler/utilities/treeSearch.ts b/src/handler/utilities/treeSearch.ts index d158ead9..16b71769 100644 --- a/src/handler/utilities/treeSearch.ts +++ b/src/handler/utilities/treeSearch.ts @@ -1,6 +1,11 @@ import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; import type { SernAutocompleteData, SernOptionsData } from '../../types/module'; +/** + * Uses an iterative DFS to check if an autocomplete node exists + * @param iAutocomplete + * @param options + */ export default function treeSearch( iAutocomplete: AutocompleteInteraction, options: SernOptionsData[] | undefined, diff --git a/src/types/module.ts b/src/types/module.ts index 54cb24ea..544e8aa9 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -20,11 +20,6 @@ import type { RoleSelectMenuInteraction, StringSelectMenuInteraction, } from 'discord.js'; -import type { - DiscordEventCommand, - ExternalEventCommand, - SernEventCommand, -} from '../handler/structures/events'; import { CommandType } from '../handler/structures/enums'; import type { Args, SlashOptions } from './handler'; import type Context from '../handler/structures/context'; @@ -32,6 +27,8 @@ import type { InitPlugin, ControlPlugin } from './plugin'; import { EventType } from '../handler/structures/enums'; import type { UserSelectMenuInteraction } from 'discord.js'; import type { AnyCommandPlugin, AnyEventPlugin } from './plugin'; +import type { SernEventsMapping } from './handler'; +import type { ClientEvents } from 'discord.js'; export interface Module { type: CommandType | EventType; @@ -114,6 +111,27 @@ export interface AutocompleteCommand execute: (ctx: AutocompleteInteraction) => Awaitable; } +export interface SernEventCommand + extends Module { + name?: T; + type: EventType.Sern; + execute(...args: SernEventsMapping[T]): Awaitable; +} + +export interface DiscordEventCommand + extends Module { + name?: T; + type: EventType.Discord; + execute(...args: ClientEvents[T]): Awaitable; +} + +export interface ExternalEventCommand extends Module { + name?: string; + emitter: string; + type: EventType.External; + execute(...args: unknown[]): Awaitable; +} + export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand; export type CommandModule = | TextCommand From edaca541c3376e62d529d2b2b3c7cb06a43e2633 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:53:31 -0600 Subject: [PATCH 37/59] pretty: prettey --- src/handler/events/observableHandling.ts | 11 +--------- src/handler/plugins/args.ts | 9 +++++--- src/handler/sern.ts | 11 ++++------ src/types/plugin.ts | 28 ++++++++++-------------- 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index fe207727..ff8362e2 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,14 +1,5 @@ import type { Awaitable, Message } from 'discord.js'; -import { - concatMap, - EMPTY, - from, - Observable, - of, - pipe, - tap, - throwError, -} from 'rxjs'; +import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs'; import type { SernError } from '../structures/errors'; import { Result } from 'ts-results-es'; import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module'; diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index 868c41d4..cc25fdf8 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -5,15 +5,18 @@ import type { BothCommand, ButtonCommand, ChannelSelectCommand, - ContextMenuUser, DiscordEventCommand, ExternalEventCommand, + ContextMenuUser, + DiscordEventCommand, + ExternalEventCommand, MentionableSelectCommand, ModalSubmitCommand, - RoleSelectCommand, SernEventCommand, + RoleSelectCommand, + SernEventCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand, - ContextMenuMsg + ContextMenuMsg, } from '../../types/module'; import type { AnyDefinedModule, Args, Payload, Processed, SlashOptions } from '../../types/handler'; import type Context from '../structures/context'; diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 4dcd2aa7..2b423b84 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -1,12 +1,7 @@ import type Wrapper from './structures/wrapper'; import { processEvents } from './events/userDefinedEventsHandling'; import { CommandType, EventType, PluginType } from './structures/enums'; -import type { - AnyEventPlugin, - ControlPlugin, - InitPlugin, - Plugin, -} from '../types/plugin'; +import type { AnyEventPlugin, ControlPlugin, InitPlugin, Plugin } from '../types/plugin'; import InteractionHandler from './events/interactionHandler'; import ReadyHandler from './events/readyHandler'; import MessageHandler from './events/messageHandler'; @@ -14,7 +9,9 @@ import type { CommandModule, CommandModuleDefs, EventModule, - EventModuleDefs, InputCommand, InputEvent + EventModuleDefs, + InputCommand, + InputEvent, } from '../types/module'; import { Container, createContainer } from 'iti'; import type { Dependencies, OptionalDependencies } from '../types/handler'; diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 797635a2..013cf3c7 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -14,15 +14,12 @@ import type { Awaitable } from 'discord.js'; import type { Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; -import type { - CommandModule, CommandModuleDefs, - EventModule, -} from './module'; +import type { CommandModule, CommandModuleDefs, EventModule } from './module'; import type { InitArgs } from '../handler/plugins'; import type { Deprecated, Processed } from './handler'; export type PluginResult = Awaitable; -export type VoidResult = Result +export type VoidResult = Result; export interface Plugin { type: PluginType; @@ -38,17 +35,16 @@ export interface ControlPlugin { execute: (...args: Args) => PluginResult; } - export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; - -export type CommandPlugin = Deprecated<'Please view alternatives: '> -export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '> -export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '> -export type SernEmitterPlugin = Deprecated<'Please view alternatives: '> -export type AutocompletePlugin = Deprecated<'Please view alternatives: '> -export type EventPlugin = Deprecated<'Please view alternatives: '> -export type SernEventPlugin = Deprecated<'Please view alternatives: '> -export type ExternalEventPlugin = Deprecated<'Please view alternatives: '> -export type DiscordEventPlugin = Deprecated<'Please view alternatives: '> +export type CommandPlugin = + Deprecated<'Please use InitPlugin instead: '>; +export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; +export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; +export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; +export type AutocompletePlugin = Deprecated<'Please view alternatives: '>; +export type EventPlugin = Deprecated<'Please view alternatives: '>; +export type SernEventPlugin = Deprecated<'Please view alternatives: '>; +export type ExternalEventPlugin = Deprecated<'Please view alternatives: '>; +export type DiscordEventPlugin = Deprecated<'Please view alternatives: '>; From 5cdc300556484a74b7808379c868c04112b12d5c Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 14:04:31 -0600 Subject: [PATCH 38/59] docs: describe file --- src/handler/events/observableHandling.ts | 4 ++-- src/handler/events/operators/index.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index ff8362e2..a279cc2c 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -103,8 +103,8 @@ export function createResultResolver< createStream: (args: Args) => Observable; }) { return (args: Args) => { - const task = config.createStream(args); - return task.pipe( + const task$ = config.createStream(args); + return task$.pipe( tap(result => { if (result.err) { config.onFailure?.(args.module); diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index 55e474d7..b515dc65 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -1,3 +1,9 @@ +/** + * This file holds sern's rxjs operators used for processing data. + * Each function should be modular and testable, not bound to discord / sern + * and independent of each other + */ + import { concatMap, defaultIfEmpty, EMPTY, every, map, of, OperatorFunction, pipe } from 'rxjs'; import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; From 7dfc22bdd2ad087df7c3f35c876ab16a3b5488c5 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 14:54:10 -0600 Subject: [PATCH 39/59] chore: update dependencies and version --- package.json | 10 +++++----- pnpm-lock.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bf102867..2714ecf0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@sern/handler", - "packageManager": "pnpm@7.22.0", - "version": "2.1.1", + "packageManager": "pnpm@7.24.3", + "version": "2.5.0", "description": "A customizable, batteries-included, powerful discord.js framework to automate and streamline bot development.", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -35,17 +35,17 @@ "dependencies": { "iti": "^0.6.0", "rxjs": "^7.5.6", - "ts-pattern": "^4.0.2", + "ts-pattern": "^4.0.6", "ts-results-es": "^3.5.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "5.47.1", "@typescript-eslint/parser": "5.48.0", + "discord.js": ">= ^14.7.x", "eslint": "8.30.0", "prettier": "2.8.2", "tsup": "^6.1.3", - "typescript": "4.9.4", - "discord.js": ">= ^14.7.x" + "typescript": "4.9.4" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23fe388c..9a575129 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,7 @@ specifiers: iti: ^0.6.0 prettier: 2.8.2 rxjs: ^7.5.6 - ts-pattern: ^4.0.2 + ts-pattern: ^4.0.6 ts-results-es: ^3.5.0 tsup: ^6.1.3 typescript: 4.9.4 From 404a8c79f103a66119c65e2c96925d9ee2124fdd Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 12 Jan 2023 19:59:51 -0600 Subject: [PATCH 40/59] docs: fix link for docasaurus --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f84436e..db81fb10 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ It is **highly encouraged** to use the [command line interface](https://github.c ## 👋 Contribute -- Read our contribution [guidelines](./.github/CONTRIBUTING.md) carefully +- Read our contribution [guidelines](https://github.com/sern-handler/handler/blob/main/.github/CONTRIBUTING.md) carefully - Pull up on [issues](https://github.com/sern-handler/handler/issues) and report bugs - All kinds of contributions are welcomed. From e5a0aac9779252d33146e04f2d2495557ddc1b8a Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 13 Jan 2023 10:43:35 -0600 Subject: [PATCH 41/59] refactor: using a more appropriate operator function for closing an observable on crash --- src/handler/contracts/errorHandling.ts | 6 ------ src/handler/events/interactionHandler.ts | 13 +++++++++++-- src/handler/events/messageHandler.ts | 13 +++++++++++-- src/handler/events/operators/index.ts | 2 +- src/handler/events/userDefinedEventsHandling.ts | 15 ++++++++++++--- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/handler/contracts/errorHandling.ts b/src/handler/contracts/errorHandling.ts index ea92ff02..9169c5bb 100644 --- a/src/handler/contracts/errorHandling.ts +++ b/src/handler/contracts/errorHandling.ts @@ -1,6 +1,5 @@ import type { Observable } from 'rxjs'; import type { Logging } from './logging'; -import { useContainerRaw } from '../dependencies/provider'; import util from 'util'; export interface ErrorHandling { /** @@ -36,11 +35,6 @@ export function handleError(crashHandler: ErrorHandling, logging?: Logging) { // This is done to fit the ErrorHandling contract const err = pload instanceof Error ? pload : Error(util.format(pload)); if (crashHandler.keepAlive == 0) { - useContainerRaw() - ?.disposeAll() - .then(() => { - logging?.info({ message: 'Cleaning container and crashing' }); - }); crashHandler.crash(err); } //formatted payload diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 2c713bf0..2cf371ae 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -1,5 +1,5 @@ import type { Interaction } from 'discord.js'; -import { catchError, concatMap, fromEvent, map, Observable } from 'rxjs'; +import { catchError, concatMap, finalize, fromEvent, map, Observable } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import { EventsHandler } from './eventsHandler'; import { SernError } from '../structures/errors'; @@ -12,6 +12,7 @@ import { handleError } from '../contracts/errorHandling'; import type { ModuleStore } from '../structures/moduleStore'; import SernEmitter from '../sernEmitter'; import type { Processed } from '../../types/handler'; +import { useContainerRaw } from '../dependencies/provider'; export default class InteractionHandler extends EventsHandler<{ event: Interaction; @@ -33,7 +34,15 @@ export default class InteractionHandler extends EventsHandler<{ ); }), concatMap(payload => executeModule(this.emitter, payload)), - catchError(handleError(this.crashHandler, this.logger)), + catchError(handleError(this.crashHandler)), + finalize(() => { + this.logger?.info({ message: 'interactionCreate stream closed or reached end of lifetime'}); + useContainerRaw() + ?.disposeAll() + .then(() => { + this.logger?.info({ message: 'Cleaning container and crashing' }); + }); + }) ) .subscribe(); } diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index f24b3fae..d71f6f5f 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -1,5 +1,5 @@ import { EventsHandler } from './eventsHandler'; -import { catchError, concatMap, EMPTY, fromEvent, map, Observable, of } from 'rxjs'; +import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import type { Message } from 'discord.js'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; @@ -11,6 +11,7 @@ import { contextArgs, dispatchCommand } from './dispatchers'; import { SernError } from '../structures/errors'; import SernEmitter from '../sernEmitter'; import type { Processed } from '../../types/handler'; +import { useContainerRaw } from '../dependencies/provider'; export default class MessageHandler extends EventsHandler<{ module: Processed; @@ -31,7 +32,15 @@ export default class MessageHandler extends EventsHandler<{ ); }), concatMap(payload => executeModule(this.emitter, payload)), - catchError(handleError(this.crashHandler, this.logger)), + catchError(handleError(this.crashHandler)), + finalize(() => { + this.logger?.info({ message: 'messageCreate stream closed or reached end of lifetime'}); + useContainerRaw() + ?.disposeAll() + .then(() => { + this.logger?.info({ message: 'Cleaning container and crashing' }); + }); + }) ) .subscribe(); } diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index b515dc65..41fdc9f3 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -21,7 +21,7 @@ export function filterMapTo(item: () => V): OperatorFunction { * Calls any plugin with {args}. * @param args if an array, its spread and plugin called. */ -export function callPlugin(args: V): OperatorFunction< +export function callPlugin(args: unknown): OperatorFunction< { execute: (...args: unknown[]) => PluginResult; }, diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 724e419c..86371821 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,4 +1,4 @@ -import { catchError, map, tap } from 'rxjs'; +import { catchError, finalize, map, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; import type { Dependencies, Processed } from '../../types/handler'; import { EventType } from '../structures/enums'; @@ -13,9 +13,10 @@ import { SernError } from '../structures/errors'; import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; import { defineAllFields } from './operators'; +import { useContainerRaw } from '../dependencies/provider'; export function processEvents({ containerConfig, events }: Wrapper) { - const [client, errorHandling, sernEmitter, logging] = containerConfig.get( + const [client, errorHandling, sernEmitter, logger] = containerConfig.get( '@sern/client', '@sern/errors', '@sern/emitter', @@ -51,7 +52,15 @@ export function processEvents({ containerConfig, events }: Wrapper) { * Where all events are turned on */ tap(dispatcher => dispatcher.subscribe()), - catchError(handleError(errorHandling, logging)), + catchError(handleError(errorHandling, logger)), + finalize(() => { + logger?.info({ message: 'an event module reached end of lifetime'}); + useContainerRaw() + ?.disposeAll() + .then(() => { + logger?.info({ message: 'Cleaning container and crashing' }); + }); + }) ) .subscribe(); } From a92efde8ce8c7a837eb7ce82a0690064d0911f81 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 14 Jan 2023 20:09:24 -0600 Subject: [PATCH 42/59] fix!: changing single and many --- src/handler/dependencies/lifetimeFunctions.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/handler/dependencies/lifetimeFunctions.ts diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts new file mode 100644 index 00000000..ffc2e954 --- /dev/null +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -0,0 +1,8 @@ + +export function single(cb: () => T) { + return cb; +} + +export function transient(cb: () => () => T) { + return cb; +} \ No newline at end of file From c14c62ad5fe0fa78ac4906e1b2cf6fa1fee698a4 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 14 Jan 2023 20:11:03 -0600 Subject: [PATCH 43/59] refactor: typings and simplifying composeRoot --- src/handler/dependencies/provider.ts | 120 +++++++++++---------------- src/handler/sern.ts | 15 ++-- src/index.ts | 2 +- src/types/handler.ts | 7 +- 4 files changed, 61 insertions(+), 83 deletions(-) diff --git a/src/handler/dependencies/provider.ts b/src/handler/dependencies/provider.ts index 90256e2b..505b0410 100644 --- a/src/handler/dependencies/provider.ts +++ b/src/handler/dependencies/provider.ts @@ -1,89 +1,67 @@ import type { Container } from 'iti'; import { SernError } from '../structures/errors'; -import { BehaviorSubject } from 'rxjs'; -import * as assert from 'assert'; -import type { Dependencies, MapDeps } from '../../types/handler'; +import type { Dependencies, DependencyConfiguration, MapDeps } from '../../types/handler'; import SernEmitter from '../sernEmitter'; -import { _const, ok } from '../utilities/functions'; -import { DefaultErrorHandling, DefaultModuleManager, Logging } from '../contracts'; +import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from '../contracts'; import { ModuleStore } from '../structures/moduleStore'; -import { Ok, Result } from 'ts-results-es'; -import { DefaultLogging } from '../contracts'; +import { Result } from 'ts-results-es'; +import { BehaviorSubject } from 'rxjs'; +import { createContainer } from 'iti'; -export const containerSubject = new BehaviorSubject -> | null>(null); -export function composeRoot( - root: Container, Partial>, - exclusion: Set, -) { - const client = root.get('@sern/client'); - assert.ok(client !== undefined, SernError.MissingRequired); - //A utility function checking if a dependency has been declared excluded - const excluded = (key: keyof Dependencies) => exclusion.has(key); - //Wraps a fetch to the container in a Result, deferring the action - const get = (key: keyof Dependencies) => Result.wrap(() => root.get(key) as T); - const getOr = (key: keyof Dependencies, elseAction: () => unknown) => { - //Gets dependency but if an Err, map to a function that upserts. - const dep = get(key).mapErr(() => elseAction); - if (dep.err) { - //Defers upsert until final check here - return dep.val(); - } - }; - const xGetOr = (key: keyof Dependencies, action: () => unknown) => { - if (excluded(key)) { - get(key) //if dev created a dependency but excluded, deletes on root composition - .andThen(() => Ok(root.delete(key))) - .unwrapOr(ok()); - } else { - getOr(key, action); - } - }; - xGetOr('@sern/emitter', () => - root.upsert({ - '@sern/emitter': _const(new SernEmitter()), - }), - ); - //An "optional" dependency - xGetOr('@sern/logger', () => { - root.upsert({ - '@sern/logger': _const(new DefaultLogging()), +export const containerSubject = new BehaviorSubject(defaultContainer()); + +export function composeRoot(conf: DependencyConfiguration) { + //Get the current container. This should have no client or logger yet. + const currentContainer = containerSubject.getValue(); + const excludeLogger = conf.exclude?.has('@sern/logger'); + if(!excludeLogger) { + currentContainer.add({ + '@sern/logger' : () => new DefaultLogging() }); - }); - xGetOr('@sern/store', () => - root.upsert({ - '@sern/store': _const(new ModuleStore()), - }), - ); - xGetOr('@sern/modules', () => - root.upsert(ctx => ({ - '@sern/modules': _const(new DefaultModuleManager(ctx['@sern/store'] as ModuleStore)), - })), - ); - xGetOr('@sern/errors', () => - root.upsert({ - '@sern/errors': _const(new DefaultErrorHandling()), - }), - ); - //If logger exists, log info, else do nothing. - get('@sern/logger') - .map(logger => logger.info({ message: 'All dependencies loaded successfully' })) - .unwrapOr(ok()); + } + //Build the container based on the callback provided by the user + const container = conf.build(currentContainer); + console.log(container); + //Check if the built container contains @sern/client or throw + // a runtime exception + const shouldHaveClient = Result + .wrap(() => container.get('@sern/client')); + shouldHaveClient.expect(SernError.MissingRequired); + + if(!excludeLogger) { + container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); + } + //I'm sorry little one + containerSubject.next(container as any); } export function useContainer() { - const container = containerSubject.getValue()! as unknown as Container; - assert.ok(container !== null, 'useContainer was called before Sern#init'); + const container = containerSubject.getValue() as Container; return (...keys: [...V]) => keys.map(key => Result.wrap(() => container.get(key)).unwrapOr(undefined)) as MapDeps; } /** * Returns the underlying data structure holding all dependencies. + * Please be careful as this only gets the client's current state. * Exposes some methods from iti */ -export function useContainerRaw() { - return containerSubject.getValue(); +export function useContainerRaw() { + return containerSubject.getValue() as Container; } + +/** + * Provides all the defaults for sern to function properly. + * The only user provided dependency needs to be @sern/client + */ +function defaultContainer() { + return createContainer() + .add({ '@sern/errors': () => new DefaultErrorHandling()}) + .add({ '@sern/store' : () => new ModuleStore()}) + .add(ctx => { + return { + '@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']) + }; + }) + .add({ '@sern/emitter': () => new SernEmitter()}) as Container, {}>; +} \ No newline at end of file diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 2b423b84..c6a0a264 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -13,9 +13,8 @@ import type { InputCommand, InputEvent, } from '../types/module'; -import { Container, createContainer } from 'iti'; -import type { Dependencies, OptionalDependencies } from '../types/handler'; -import { composeRoot, containerSubject, useContainer } from './dependencies/provider'; +import type { Dependencies, DependencyConfiguration } from '../types/handler'; +import { composeRoot, useContainer } from './dependencies/provider'; import type { Logging } from './contracts'; import { err, ok, partition } from './utilities/functions'; import type { Awaitable, ClientEvents } from 'discord.js'; @@ -105,13 +104,9 @@ export function discordEvent(mod: { /** * @param conf a configuration for creating your project dependencies */ -export function makeDependencies(conf: { - exclude?: Set; - build: (root: Container, {}>) => Container, T>; -}) { - const container = conf.build(createContainer()); - composeRoot(container, conf.exclude ?? new Set()); - containerSubject.next(container as unknown as Container); +export function makeDependencies(conf: DependencyConfiguration) { + //Until there are more optional dependencies, just check if the logger exists + composeRoot(conf); return useContainer(); } diff --git a/src/index.ts b/src/index.ts index 7d9d229d..7d168b3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,5 +15,5 @@ export * from './handler/structures/structxports'; export * from './handler/plugins'; export * from './handler/contracts'; export { SernEmitter }; -export { _const as single, transient as many } from './handler/utilities/functions'; export { useContainerRaw } from './handler/dependencies/provider'; +export { single, transient } from './handler/dependencies/lifetimeFunctions'; diff --git a/src/types/handler.ts b/src/types/handler.ts index 9ee0e27b..caf9af13 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -7,6 +7,7 @@ import type { UnpackFunction } from 'iti'; import type { ErrorHandling, Logging, ModuleManager } from '../handler/contracts'; import type { ModuleStore } from '../handler/structures/moduleStore'; import type SernEmitter from '../handler/sernEmitter'; +import type { Container } from 'iti'; // Thanks to @kelsny export type ParseType = { [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; @@ -61,4 +62,8 @@ export type MapDeps = T //Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest export type OptionalDependencies = '@sern/logger'; export type Processed = T & { name: string; description: string }; -export type Deprecated = [never, Message] \ No newline at end of file +export type Deprecated = [never, Message] +export interface DependencyConfiguration { + exclude?: Set; + build: (root: Container, {}>) => Container +} \ No newline at end of file From c34477d9df6e04118a8536ea386db798f3b0765d Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 14 Jan 2023 20:11:23 -0600 Subject: [PATCH 44/59] fix: re-add logger into handleError --- src/handler/events/interactionHandler.ts | 2 +- src/handler/events/messageHandler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 2cf371ae..736cd146 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -34,7 +34,7 @@ export default class InteractionHandler extends EventsHandler<{ ); }), concatMap(payload => executeModule(this.emitter, payload)), - catchError(handleError(this.crashHandler)), + catchError(handleError(this.crashHandler, this.logger)), finalize(() => { this.logger?.info({ message: 'interactionCreate stream closed or reached end of lifetime'}); useContainerRaw() diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index d71f6f5f..c787258d 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -32,7 +32,7 @@ export default class MessageHandler extends EventsHandler<{ ); }), concatMap(payload => executeModule(this.emitter, payload)), - catchError(handleError(this.crashHandler)), + catchError(handleError(this.crashHandler, this.logger)), finalize(() => { this.logger?.info({ message: 'messageCreate stream closed or reached end of lifetime'}); useContainerRaw() From a82a9147414a146c4de97800bbaa907950fe7100 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 14 Jan 2023 20:27:12 -0600 Subject: [PATCH 45/59] docs: comment --- src/handler/dependencies/provider.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/handler/dependencies/provider.ts b/src/handler/dependencies/provider.ts index 505b0410..931a4c17 100644 --- a/src/handler/dependencies/provider.ts +++ b/src/handler/dependencies/provider.ts @@ -10,6 +10,12 @@ import { createContainer } from 'iti'; export const containerSubject = new BehaviorSubject(defaultContainer()); +/** + * Given the user's conf, check for any excluded dependency keys. + * Then, call conf.build to get the rest of the users' dependencies. + * Finally, update the containerSubject with the new container state + * @param conf + */ export function composeRoot(conf: DependencyConfiguration) { //Get the current container. This should have no client or logger yet. const currentContainer = containerSubject.getValue(); @@ -21,7 +27,6 @@ export function composeRoot(conf: DependencyConfiguratio } //Build the container based on the callback provided by the user const container = conf.build(currentContainer); - console.log(container); //Check if the built container contains @sern/client or throw // a runtime exception const shouldHaveClient = Result From c51568cfaa93436b50d6a20bbfe7dc39b287c7a7 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:12:31 -0600 Subject: [PATCH 46/59] docs: new section --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db81fb10..26cfc136 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,13 @@ yarn add @sern/handler ```sh pnpm add @sern/handler ``` - +## Why? +- Most handlers don't support discord.js 14.7+ +- Customizable commands +- Plug and play or customize to your liking +- Embraces reactive programming for consistent and reliable backend +- Customizable logger, error handling, and more +- Active development and growing [community](https://sern.dev/discord) ## 👀 Quick Look * Support for discord.js v14 and all interactions From d46e3c58b0f57d0f68cd424adbee84775de8797d Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:12:44 -0600 Subject: [PATCH 47/59] feat: help mitigate breaking changes --- src/handler/dependencies/lifetimeFunctions.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts index ffc2e954..8d5f3e0a 100644 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -1,8 +1,17 @@ - -export function single(cb: () => T) { - return cb; +/** + * Following iti's singleton and transient implementation, + * use single if you want a singleton object + * @param cb + */ +export function single(cb: (() => T) | T) { + if(typeof cb === 'function') + return cb; } - +/** + * Following iti's singleton and transient implementation, + * use transient if you want a new dependency every time your container getter is called + * @param cb + */ export function transient(cb: () => () => T) { return cb; } \ No newline at end of file From d79c085b0efb421a1511223e96641ba12e8959ee Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:16:49 -0600 Subject: [PATCH 48/59] feat: help mitigate breaking changes --- src/handler/dependencies/lifetimeFunctions.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts index 8d5f3e0a..5d0c7c82 100644 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -1,3 +1,10 @@ + +/** + * @deprecated + * @param cb + * Deprecated signature: please pass a callback instead of just a dependency + */ +export function single(cb: T) : T /** * Following iti's singleton and transient implementation, * use single if you want a singleton object @@ -7,11 +14,18 @@ export function single(cb: (() => T) | T) { if(typeof cb === 'function') return cb; } +/** + * @deprecated + * @param cb + * Deprecated signature + */ +export function transient(cb: T) : () => () => T /** * Following iti's singleton and transient implementation, * use transient if you want a new dependency every time your container getter is called * @param cb */ -export function transient(cb: () => () => T) { +export function transient(cb: (() => () => T) | T) { + if(typeof cb !== 'function') return () => () => cb; return cb; } \ No newline at end of file From c3e55a7af6cf2c70dbc488815959435c510f1576 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 10:44:21 -0600 Subject: [PATCH 49/59] feat: help mitigate breaking changes and function overloads --- src/handler/dependencies/lifetimeFunctions.ts | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts index 5d0c7c82..b3f6e11d 100644 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -1,25 +1,34 @@ +type NotFunction = string | number | boolean | null | undefined | bigint | + readonly any[] | { apply?: never, [k: string]: any } | + { call?: never, [k: string]: any }; /** * @deprecated * @param cb - * Deprecated signature: please pass a callback instead of just a dependency */ -export function single(cb: T) : T +export function single(cb: T) : () => T; /** - * Following iti's singleton and transient implementation, - * use single if you want a singleton object + * New signature + * @param cb + */ +export function single unknown>(cb: T) : T; +/** + * Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and + * very annoying). + * For future versions, ensure that single is being passed as a **callback!!** * @param cb */ -export function single(cb: (() => T) | T) { - if(typeof cb === 'function') - return cb; +export function single(cb: T) { + if(typeof cb === 'function') return cb; + return () => cb; } /** * @deprecated * @param cb * Deprecated signature */ -export function transient(cb: T) : () => () => T +export function transient(cb: T) : () => () => T +export function transient () => unknown>(cb: T) : T; /** * Following iti's singleton and transient implementation, * use transient if you want a new dependency every time your container getter is called From 98f4978da8dbc7aec4d97c48b48d17222b5627a3 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 11:55:10 -0600 Subject: [PATCH 50/59] feat: deprecate instead of remove --- src/handler/structures/enums.ts | 10 ++++++++++ src/types/plugin.ts | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/handler/structures/enums.ts b/src/handler/structures/enums.ts index f843f1fb..d4dd804d 100644 --- a/src/handler/structures/enums.ts +++ b/src/handler/structures/enums.ts @@ -105,6 +105,16 @@ export enum PluginType { * The PluginType for InitPlugins */ Init = 1, + /** + * @deprecated + * Use PluginType.Init instead + */ + Command = 1, + /** + * @deprecated + * Use PluginType.Control instead + */ + Event = 2, /** * The PluginType for EventPlugins */ diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 013cf3c7..1ec8bd4e 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -14,10 +14,11 @@ import type { Awaitable } from 'discord.js'; import type { Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; -import type { CommandModule, CommandModuleDefs, EventModule } from './module'; +import type { CommandModule, EventModule } from './module'; import type { InitArgs } from '../handler/plugins'; import type { Deprecated, Processed } from './handler'; +import type { CommandType } from '../handler/structures/enums'; export type PluginResult = Awaitable; export type VoidResult = Result; @@ -38,13 +39,13 @@ export interface ControlPlugin { export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; -export type CommandPlugin = +export type CommandPlugin = Deprecated<'Please use InitPlugin instead: '>; export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; export type AutocompletePlugin = Deprecated<'Please view alternatives: '>; -export type EventPlugin = Deprecated<'Please view alternatives: '>; +export type EventPlugin = Deprecated<'Please view alternatives: '>; export type SernEventPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEventPlugin = Deprecated<'Please view alternatives: '>; export type DiscordEventPlugin = Deprecated<'Please view alternatives: '>; From 45edb98e7cd2b7a82310433448eebf452ae1ee0a Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 12:25:19 -0600 Subject: [PATCH 51/59] feat: partial remove and deprecate old symbols --- src/types/plugin.ts | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 1ec8bd4e..83b57685 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -14,9 +14,9 @@ import type { Awaitable } from 'discord.js'; import type { Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; -import type { CommandModule, EventModule } from './module'; +import type { CommandModule, CommandModuleDefs, EventModule } from './module'; -import type { InitArgs } from '../handler/plugins'; +import type { CommandArgs, InitArgs } from '../handler/plugins'; import type { Deprecated, Processed } from './handler'; import type { CommandType } from '../handler/structures/enums'; export type PluginResult = Awaitable; @@ -39,13 +39,42 @@ export interface ControlPlugin { export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; -export type CommandPlugin = - Deprecated<'Please use InitPlugin instead: '>; +/** + * @deprecated + * Use the newer helper functions + */ +export interface CommandPlugin { + /** + * @deprecated + */ + name?: string + /** + * @deprecated + */ + description?: string; + type: PluginType.Command, + execute: (args: { mod: Processed; absPath: string }, controller : Deprecated<'Import controller from @sern/handler instead'>) => Awaitable +} +/** + * @deprecated + * Use the newer helper functions + */ +export interface EventPlugin { + /** + * @deprecated + */ + name?: string; + /** + * @deprecated + */ + description?: string; + type: PluginType.Event, + execute: (args: CommandArgs, controller : Deprecated<'Import controller from @sern/handler instead'>) => Awaitable +} export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; export type AutocompletePlugin = Deprecated<'Please view alternatives: '>; -export type EventPlugin = Deprecated<'Please view alternatives: '>; export type SernEventPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEventPlugin = Deprecated<'Please view alternatives: '>; export type DiscordEventPlugin = Deprecated<'Please view alternatives: '>; From a11c5e4fa0f2c322c2ac0c253e1632d640e50762 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 13:21:04 -0600 Subject: [PATCH 52/59] revert: trying to accommodate old plugins is too difficult --- src/types/plugin.ts | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 83b57685..7d6115ab 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -14,9 +14,9 @@ import type { Awaitable } from 'discord.js'; import type { Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; -import type { CommandModule, CommandModuleDefs, EventModule } from './module'; +import type { CommandModule, EventModule } from './module'; -import type { CommandArgs, InitArgs } from '../handler/plugins'; +import type { InitArgs } from '../handler/plugins'; import type { Deprecated, Processed } from './handler'; import type { CommandType } from '../handler/structures/enums'; export type PluginResult = Awaitable; @@ -43,34 +43,12 @@ export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs { - /** - * @deprecated - */ - name?: string - /** - * @deprecated - */ - description?: string; - type: PluginType.Command, - execute: (args: { mod: Processed; absPath: string }, controller : Deprecated<'Import controller from @sern/handler instead'>) => Awaitable -} +export interface CommandPlugin {} /** * @deprecated * Use the newer helper functions */ -export interface EventPlugin { - /** - * @deprecated - */ - name?: string; - /** - * @deprecated - */ - description?: string; - type: PluginType.Event, - execute: (args: CommandArgs, controller : Deprecated<'Import controller from @sern/handler instead'>) => Awaitable -} +export interface EventPlugin {} export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; From 32678ca37015cd77c1f8b416c06230657009084a Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:11:01 -0600 Subject: [PATCH 53/59] docs: add many as deprecated --- src/handler/dependencies/lifetimeFunctions.ts | 11 ++++++++++- src/handler/utilities/functions.ts | 3 +-- src/index.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts index b3f6e11d..d7c4d6e6 100644 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -1,3 +1,5 @@ +import { _const } from '../utilities/functions'; + type NotFunction = string | number | boolean | null | undefined | bigint | readonly any[] | { apply?: never, [k: string]: any } | { call?: never, [k: string]: any }; @@ -37,4 +39,11 @@ export function transient () => unknown>(cb: T) : T; export function transient(cb: (() => () => T) | T) { if(typeof cb !== 'function') return () => () => cb; return cb; -} \ No newline at end of file +} + +/** + * @deprecated + * @param value + */ +// prettier-ignore +export const many = (value: T) => () => _const(value); diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts index cfd4cbb9..557d8ca2 100644 --- a/src/handler/utilities/functions.ts +++ b/src/handler/utilities/functions.ts @@ -13,8 +13,7 @@ export const _const = (value: T) => () => value; * Used for transient in iti * @param value */ -// prettier-ignore -export const transient = (value: T) => () => _const(value); + export function nameOrFilename(modName: string | undefined, absPath: string) { return modName ?? Files.fmtFileName(basename(absPath)); diff --git a/src/index.ts b/src/index.ts index 7d168b3d..fd68ccc7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,4 +16,4 @@ export * from './handler/plugins'; export * from './handler/contracts'; export { SernEmitter }; export { useContainerRaw } from './handler/dependencies/provider'; -export { single, transient } from './handler/dependencies/lifetimeFunctions'; +export { single, transient, many } from './handler/dependencies/lifetimeFunctions'; \ No newline at end of file From 9e8b17a3d113c2ba09bca573564c76ef3c7bf291 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:15:19 -0600 Subject: [PATCH 54/59] docs: update --- src/handler/utilities/functions.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts index 557d8ca2..fbee0b11 100644 --- a/src/handler/utilities/functions.ts +++ b/src/handler/utilities/functions.ts @@ -3,18 +3,16 @@ import { basename } from 'path'; import { Err, Ok } from 'ts-results-es'; /** * A function that returns whatever value is provided. - * Used for singleton in iti + * Warning: this evaluates { @param value }. It does not defer a value. * @param value */ // prettier-ignore export const _const = (value: T) => () => value; /** - * A function that returns another function - * Used for transient in iti - * @param value + * + * @param modName + * @param absPath */ - - export function nameOrFilename(modName: string | undefined, absPath: string) { return modName ?? Files.fmtFileName(basename(absPath)); } From 83b00424fdf569b57a6ee68cd9c4bc31d916fb42 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:14:23 -0600 Subject: [PATCH 55/59] feat: partial backwards compatability --- src/handler/events/operators/index.ts | 13 ++++++++++--- src/handler/plugins/args.ts | 6 +++--- src/handler/plugins/createPlugin.ts | 5 +++-- src/types/handler.ts | 4 ++-- src/types/plugin.ts | 25 +++++++++++++++++++------ 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts index 41fdc9f3..73edbc75 100644 --- a/src/handler/events/operators/index.ts +++ b/src/handler/events/operators/index.ts @@ -8,6 +8,8 @@ import { concatMap, defaultIfEmpty, EMPTY, every, map, of, OperatorFunction, pip import type { AnyModule } from '../../../types/module'; import { nameOrFilename } from '../../utilities/functions'; import type { PluginResult, VoidResult } from '../../../types/plugin'; +import { guayin } from '../../plugins'; +import { controller } from '../../sern'; /** * if {src} is true, mapTo V, else ignore @@ -29,10 +31,15 @@ export function callPlugin(args: unknown): OperatorFunction< > { return pipe( concatMap(async plugin => { - if (Array.isArray(args)) { - return plugin.execute(...args); + const isNewPlugin = Reflect.has(plugin, guayin); + if(isNewPlugin) { + if (Array.isArray(args)) { + return plugin.execute(...args); + } + return plugin.execute(args); + } else { + return plugin.execute(args, controller); } - return plugin.execute(args); }), ); } diff --git a/src/handler/plugins/args.ts b/src/handler/plugins/args.ts index cc25fdf8..e12e46ad 100644 --- a/src/handler/plugins/args.ts +++ b/src/handler/plugins/args.ts @@ -16,9 +16,9 @@ import type { StringSelectCommand, TextCommand, UserSelectCommand, - ContextMenuMsg, + ContextMenuMsg, Module, } from '../../types/module'; -import type { AnyDefinedModule, Args, Payload, Processed, SlashOptions } from '../../types/handler'; +import type { Args, Payload, Processed, SlashOptions } from '../../types/handler'; import type Context from '../structures/context'; import type { MessageContextMenuCommandInteraction } from 'discord.js'; import type { @@ -101,7 +101,7 @@ type EventArgsMatrix = { }; }; -export interface InitArgs { +export interface InitArgs> { module: T; absPath: string; } diff --git a/src/handler/plugins/createPlugin.ts b/src/handler/plugins/createPlugin.ts index 9140a344..01a806dd 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/handler/plugins/createPlugin.ts @@ -2,7 +2,7 @@ import { CommandType, EventType, PluginType } from '../structures/enums'; import type { Plugin, PluginResult } from '../../types/plugin'; import type { CommandArgs, EventArgs } from './args'; import type { ClientEvents } from 'discord.js'; - +export const guayin = Symbol('twice<3'); export function makePlugin( type: PluginType, execute: (...args: any[]) => any, @@ -10,7 +10,8 @@ export function makePlugin( return { type, execute, - }; + [guayin]: undefined + } as Plugin; } export function EventInitPlugin( diff --git a/src/types/handler.ts b/src/types/handler.ts index caf9af13..e1288033 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -2,7 +2,7 @@ import type { CommandInteractionOptionResolver } from 'discord.js'; import type { PayloadType } from '../handler/structures/enums'; import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { EventEmitter } from 'events'; -import type { CommandModule, EventModule, AnyModule, Module } from './module'; +import type { CommandModule, EventModule, AnyModule } from './module'; import type { UnpackFunction } from 'iti'; import type { ErrorHandling, Logging, ModuleManager } from '../handler/contracts'; import type { ModuleStore } from '../handler/structures/moduleStore'; @@ -61,7 +61,7 @@ export type MapDeps = T : [never]; //Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest export type OptionalDependencies = '@sern/logger'; -export type Processed = T & { name: string; description: string }; +export type Processed = T & { name: string; description: string }; export type Deprecated = [never, Message] export interface DependencyConfiguration { exclude?: Set; diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 7d6115ab..a55c5040 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -12,16 +12,19 @@ */ import type { Awaitable } from 'discord.js'; -import type { Result } from 'ts-results-es'; +import type { Err, Ok, Result } from 'ts-results-es'; import type { PluginType } from '../handler/structures/enums'; import type { CommandModule, EventModule } from './module'; - -import type { InitArgs } from '../handler/plugins'; +import type { CommandArgs, InitArgs } from '../handler/plugins'; import type { Deprecated, Processed } from './handler'; import type { CommandType } from '../handler/structures/enums'; export type PluginResult = Awaitable; export type VoidResult = Result; +export interface Controller { + next: () => Ok + stop: () => Err +} export interface Plugin { type: PluginType; execute: (...args: Args) => PluginResult; @@ -41,14 +44,24 @@ export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs {} +export interface CommandPlugin { + name?: string; + description?: string; + type: PluginType.Command; + execute: (m: InitArgs>, controller?: Deprecated<'Please import controller instead'>) => PluginResult; +} /** * @deprecated * Use the newer helper functions */ -export interface EventPlugin {} +export interface EventPlugin { + name?: string; + description?: string; + type: PluginType.Event; + execute: (args : CommandArgs, controller?: Controller) => PluginResult +} export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; From 443ac09d175ca985051dcdb86e5287dcbfb84796 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:42:23 -0600 Subject: [PATCH 56/59] refactor: renaming, docs, and exports more clean --- src/handler/dependencies/index.ts | 3 ++- src/handler/dependencies/lifetimeFunctions.ts | 1 + src/handler/dependencies/provider.ts | 8 ++++---- src/handler/structures/{structxports.ts => index.ts} | 0 src/index.ts | 5 ++--- 5 files changed, 9 insertions(+), 8 deletions(-) rename src/handler/structures/{structxports.ts => index.ts} (100%) diff --git a/src/handler/dependencies/index.ts b/src/handler/dependencies/index.ts index bf85ade5..0d8a416e 100644 --- a/src/handler/dependencies/index.ts +++ b/src/handler/dependencies/index.ts @@ -1 +1,2 @@ -export { useContainer } from './provider'; +export { single, transient, many } from './lifetimeFunctions'; +export { useContainerRaw } from './provider'; \ No newline at end of file diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts index d7c4d6e6..f608030a 100644 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ b/src/handler/dependencies/lifetimeFunctions.ts @@ -44,6 +44,7 @@ export function transient(cb: (() => () => T) | T) { /** * @deprecated * @param value + * Please use the transient function instead */ // prettier-ignore export const many = (value: T) => () => _const(value); diff --git a/src/handler/dependencies/provider.ts b/src/handler/dependencies/provider.ts index 931a4c17..d199a804 100644 --- a/src/handler/dependencies/provider.ts +++ b/src/handler/dependencies/provider.ts @@ -17,7 +17,7 @@ export const containerSubject = new BehaviorSubject(defaultContainer()); * @param conf */ export function composeRoot(conf: DependencyConfiguration) { - //Get the current container. This should have no client or logger yet. + //Get the current container. This should have no client or possible logger yet. const currentContainer = containerSubject.getValue(); const excludeLogger = conf.exclude?.has('@sern/logger'); if(!excludeLogger) { @@ -29,9 +29,9 @@ export function composeRoot(conf: DependencyConfiguratio const container = conf.build(currentContainer); //Check if the built container contains @sern/client or throw // a runtime exception - const shouldHaveClient = Result - .wrap(() => container.get('@sern/client')); - shouldHaveClient.expect(SernError.MissingRequired); + Result + .wrap(() => container.get('@sern/client')) + .expect(SernError.MissingRequired); if(!excludeLogger) { container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); diff --git a/src/handler/structures/structxports.ts b/src/handler/structures/index.ts similarity index 100% rename from src/handler/structures/structxports.ts rename to src/handler/structures/index.ts diff --git a/src/index.ts b/src/index.ts index fd68ccc7..dcb877ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,9 +11,8 @@ export * as Sern from './handler/sern'; export * from './types/handler'; export * from './types/module'; export * from './types/plugin'; -export * from './handler/structures/structxports'; +export * from './handler/structures'; export * from './handler/plugins'; export * from './handler/contracts'; export { SernEmitter }; -export { useContainerRaw } from './handler/dependencies/provider'; -export { single, transient, many } from './handler/dependencies/lifetimeFunctions'; \ No newline at end of file +export * from './handler/dependencies'; \ No newline at end of file From b60cc96a59fd9e93f8c8f00757f56605582dca2f Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:50:49 -0600 Subject: [PATCH 57/59] refactor: context got a lot simpler --- src/handler/structures/context.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handler/structures/context.ts b/src/handler/structures/context.ts index c7a12ce2..d87b69be 100644 --- a/src/handler/structures/context.ts +++ b/src/handler/structures/context.ts @@ -39,11 +39,11 @@ export default class Context { } public get id(): Snowflake { - return safeUnwrap(this.ctx.map(m => m.id).mapErr(i => i.id)); + return this.ctx.val.id; } public get channel() { - return safeUnwrap(this.ctx.map(m => m.channel).mapErr(i => i.channel)); + return this.ctx.val.channel; } /** * If context is holding a message, message.author @@ -54,30 +54,30 @@ export default class Context { } public get createdTimestamp(): number { - return safeUnwrap(this.ctx.map(m => m.createdTimestamp).mapErr(i => i.createdTimestamp)); + return this.ctx.val.createdTimestamp; } public get guild() { - return safeUnwrap(this.ctx.map(m => m.guild).mapErr(i => i.guild)); + return this.ctx.val.guild; } public get guildId() { - return safeUnwrap(this.ctx.map(m => m.guildId).mapErr(i => i.guildId)); + return this.ctx.val.guildId; } /* * interactions can return APIGuildMember if the guild it is emitted from is not cached */ public get member() { - return safeUnwrap(this.ctx.map(m => m.member).mapErr(i => i.member)); + return this.ctx.val.member; } public get client(): Client { - return safeUnwrap(this.ctx.map(m => m.client).mapErr(i => i.client)); + return this.ctx.val.client; } public get inGuild(): boolean { - return safeUnwrap(this.ctx.map(m => m.inGuild()).mapErr(i => i.inGuild())); + return this.ctx.val.inGuild(); } public isMessage() { return this.ctx.map(() => true).unwrapOr(false); From a6ac00fc456938cf64b5cb7a2ba3c1c347b55f31 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:31:12 -0600 Subject: [PATCH 58/59] refactor: imports --- src/handler/contracts/moduleManager.ts | 3 +-- src/handler/events/dispatchers/dispatchers.ts | 2 +- src/handler/events/dispatchers/provideArgs.ts | 2 +- src/handler/events/interactionHandler.ts | 6 ++---- src/handler/events/messageHandler.ts | 6 ++---- src/handler/events/observableHandling.ts | 2 +- src/handler/events/readyHandler.ts | 4 +--- src/handler/events/userDefinedEventsHandling.ts | 6 ++---- src/handler/structures/index.ts | 2 +- 9 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/handler/contracts/moduleManager.ts b/src/handler/contracts/moduleManager.ts index 5ba2086f..2c74937b 100644 --- a/src/handler/contracts/moduleManager.ts +++ b/src/handler/contracts/moduleManager.ts @@ -1,6 +1,5 @@ import type { CommandModuleDefs } from '../../types/module'; -import type { CommandType } from '../structures/enums'; -import type { ModuleStore } from '../structures/moduleStore'; +import type { CommandType, ModuleStore } from '../structures'; import type { Processed } from '../../types/handler'; export interface ModuleManager { diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index 5fd12e0c..b1acbed5 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -1,6 +1,6 @@ import type { Processed } from '../../../types/handler'; import type { AutocompleteInteraction } from 'discord.js'; -import { SernError } from '../../structures/errors'; +import { SernError } from '../../structures'; import treeSearch from '../../utilities/treeSearch'; import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../types/module'; import { EventEmitter } from 'events'; diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts index 21063261..33aa91e0 100644 --- a/src/handler/events/dispatchers/provideArgs.ts +++ b/src/handler/events/dispatchers/provideArgs.ts @@ -1,5 +1,5 @@ import type { ChatInputCommandInteraction, Interaction, Message } from 'discord.js'; -import Context from '../../structures/context'; +import { Context } from '../../structures'; import type { Args, SlashOptions } from '../../../types/handler'; /** diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts index 736cd146..450e740a 100644 --- a/src/handler/events/interactionHandler.ts +++ b/src/handler/events/interactionHandler.ts @@ -2,17 +2,15 @@ import type { Interaction } from 'discord.js'; import { catchError, concatMap, finalize, fromEvent, map, Observable } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import { EventsHandler } from './eventsHandler'; -import { SernError } from '../structures/errors'; -import { CommandType } from '../structures/enums'; +import { CommandType, SernError, type ModuleStore } from '../structures'; import { match, P } from 'ts-pattern'; import { contextArgs, interactionArg, dispatchAutocomplete, dispatchCommand } from './dispatchers'; import { executeModule, makeModuleExecutor } from './observableHandling'; import type { CommandModule } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; -import type { ModuleStore } from '../structures/moduleStore'; import SernEmitter from '../sernEmitter'; import type { Processed } from '../../types/handler'; -import { useContainerRaw } from '../dependencies/provider'; +import { useContainerRaw } from '../dependencies'; export default class InteractionHandler extends EventsHandler<{ event: Interaction; diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts index c787258d..ddb4eadd 100644 --- a/src/handler/events/messageHandler.ts +++ b/src/handler/events/messageHandler.ts @@ -1,17 +1,15 @@ import { EventsHandler } from './eventsHandler'; import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of } from 'rxjs'; -import type Wrapper from '../structures/wrapper'; +import { type Wrapper, type ModuleStore, SernError } from '../structures'; import type { Message } from 'discord.js'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import { fmt } from '../utilities/messageHelpers'; import type { CommandModule, Module, TextCommand } from '../../types/module'; import { handleError } from '../contracts/errorHandling'; -import type { ModuleStore } from '../structures/moduleStore'; import { contextArgs, dispatchCommand } from './dispatchers'; -import { SernError } from '../structures/errors'; import SernEmitter from '../sernEmitter'; import type { Processed } from '../../types/handler'; -import { useContainerRaw } from '../dependencies/provider'; +import { useContainerRaw } from '../dependencies'; export default class MessageHandler extends EventsHandler<{ module: Processed; diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index a279cc2c..d627fe56 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,6 +1,6 @@ import type { Awaitable, Message } from 'discord.js'; import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs'; -import type { SernError } from '../structures/errors'; +import type { SernError } from '../structures'; import { Result } from 'ts-results-es'; import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module'; import { _const as i } from '../utilities/functions'; diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts index 512184da..fd6856e5 100644 --- a/src/handler/events/readyHandler.ts +++ b/src/handler/events/readyHandler.ts @@ -3,15 +3,13 @@ import type Wrapper from '../structures/wrapper'; import { concatMap, fromEvent, type Observable, take } from 'rxjs'; import * as Files from '../utilities/readFile'; import { errTap, scanModule } from './observableHandling'; -import { CommandType } from '../structures/enums'; -import { SernError } from '../structures/errors'; +import { CommandType, SernError, type ModuleStore } from '../structures'; import { match } from 'ts-pattern'; import { Result } from 'ts-results-es'; import { ApplicationCommandType, ComponentType } from 'discord.js'; import type { CommandModule } from '../../types/module'; import type { Processed } from '../../types/handler'; import type { ModuleManager } from '../contracts'; -import type { ModuleStore } from '../structures/moduleStore'; import { _const, err, ok } from '../utilities/functions'; import { defineAllFields } from './operators'; import SernEmitter from '../sernEmitter'; diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts index 86371821..e49f45ba 100644 --- a/src/handler/events/userDefinedEventsHandling.ts +++ b/src/handler/events/userDefinedEventsHandling.ts @@ -1,19 +1,17 @@ import { catchError, finalize, map, tap } from 'rxjs'; import { buildData } from '../utilities/readFile'; import type { Dependencies, Processed } from '../../types/handler'; -import { EventType } from '../structures/enums'; -import type Wrapper from '../structures/wrapper'; import { errTap, scanModule } from './observableHandling'; import type { CommandModule, EventModule } from '../../types/module'; import type { EventEmitter } from 'events'; import SernEmitter from '../sernEmitter'; import { match } from 'ts-pattern'; import type { ErrorHandling, Logging } from '../contracts'; -import { SernError } from '../structures/errors'; +import { SernError, EventType, type Wrapper } from '../structures'; import { eventDispatcher } from './dispatchers'; import { handleError } from '../contracts/errorHandling'; import { defineAllFields } from './operators'; -import { useContainerRaw } from '../dependencies/provider'; +import { useContainerRaw } from '../dependencies'; export function processEvents({ containerConfig, events }: Wrapper) { const [client, errorHandling, sernEmitter, logger] = containerConfig.get( diff --git a/src/handler/structures/index.ts b/src/handler/structures/index.ts index 97219c65..917094ce 100644 --- a/src/handler/structures/index.ts +++ b/src/handler/structures/index.ts @@ -1,6 +1,6 @@ import Context from './context'; import type Wrapper from './wrapper'; import { ModuleStore } from './moduleStore'; - +export * from './errors'; export * from './enums'; export { Context, Wrapper, ModuleStore }; From b64147281ba8f857e8a9cf0a07d67ad654645f94 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:33:06 -0600 Subject: [PATCH 59/59] docs: explain methods --- src/handler/sernEmitter.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/handler/sernEmitter.ts b/src/handler/sernEmitter.ts index 07dc97c0..0a602ec7 100644 --- a/src/handler/sernEmitter.ts +++ b/src/handler/sernEmitter.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import type { Payload, SernEventsMapping } from '../types/handler'; -import { PayloadType } from './structures/enums'; +import { PayloadType } from './structures'; import type { Module } from '../types/module'; class SernEmitter extends EventEmitter { @@ -44,6 +44,12 @@ class SernEmitter extends EventEmitter { ) { return { type, module, reason } as T; } + + /** + * Creates a compliant SernEmitter failure payload + * @param module + * @param reason + */ static failure(module?: Module, reason?: unknown) { //The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload // same goes to the other methods below @@ -53,12 +59,20 @@ class SernEmitter extends EventEmitter { reason, ); } + /** + * Creates a compliant SernEmitter module success payload + * @param module + */ static success(module: Module) { return SernEmitter.payload( PayloadType.Success, module, ); } + /** + * Creates a compliant SernEmitter module warning payload + * @param reason + */ static warning(reason: unknown) { return SernEmitter.payload( PayloadType.Warning,