From 8a537d670b034166cd1b4c26395297e7ad5d1f02 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 9 May 2023 22:49:29 -0500 Subject: [PATCH] feat: types organization and cleaning up code base --- src/core/contracts/error-handling.ts | 13 -- src/core/contracts/index.ts | 6 +- src/core/contracts/logging.ts | 22 +-- src/core/contracts/module-manager.ts | 31 +--- src/core/create-plugins.ts | 2 +- src/core/dependencies.ts | 152 ------------------ src/core/functions.ts | 2 +- src/core/index.ts | 32 +++- src/core/ioc/base.ts | 39 +++++ src/core/ioc/dependency-injection.ts | 78 +++++++++ src/core/ioc/index.ts | 3 + src/core/ioc/types.ts | 47 ++++++ src/core/module-loading.ts | 5 +- src/core/operators.ts | 4 +- src/core/structures/container.ts | 45 ++++++ src/core/structures/context.ts | 2 +- src/core/structures/index.ts | 1 + src/core/structures/sern-emitter.ts | 4 +- .../structures/services/error-handling.ts | 14 ++ src/core/structures/services/index.ts | 3 + src/core/structures/services/logger.ts | 23 +++ .../structures/services/module-manager.ts | 32 ++++ .../module.ts => core/types/modules.ts} | 91 ++--------- src/core/types/plugins.ts | 119 ++++++++++++++ src/{ => handler}/commands.ts | 12 +- src/handler/events/dispatchers.ts | 11 +- src/handler/events/generic.ts | 11 +- src/handler/events/interactions.ts | 7 +- src/handler/events/messages.ts | 4 +- src/handler/events/ready.ts | 18 ++- src/handler/events/user-defined.ts | 24 ++- src/handler/sern.ts | 44 ++--- src/handler/types.ts | 23 +++ src/index.ts | 6 +- src/{types/handler.ts => shared.ts} | 48 ++++-- src/types/core.ts | 80 --------- src/types/plugin.ts | 52 ------ tsup.config.js | 9 +- yarn.lock | 8 - 39 files changed, 591 insertions(+), 536 deletions(-) delete mode 100644 src/core/dependencies.ts create mode 100644 src/core/ioc/base.ts create mode 100644 src/core/ioc/dependency-injection.ts create mode 100644 src/core/ioc/index.ts create mode 100644 src/core/ioc/types.ts create mode 100644 src/core/structures/container.ts create mode 100644 src/core/structures/services/error-handling.ts create mode 100644 src/core/structures/services/index.ts create mode 100644 src/core/structures/services/logger.ts create mode 100644 src/core/structures/services/module-manager.ts rename src/{types/module.ts => core/types/modules.ts} (67%) create mode 100644 src/core/types/plugins.ts rename src/{ => handler}/commands.ts (91%) create mode 100644 src/handler/types.ts rename src/{types/handler.ts => shared.ts} (68%) delete mode 100644 src/types/core.ts delete mode 100644 src/types/plugin.ts diff --git a/src/core/contracts/error-handling.ts b/src/core/contracts/error-handling.ts index f905c2e..82d686e 100644 --- a/src/core/contracts/error-handling.ts +++ b/src/core/contracts/error-handling.ts @@ -19,17 +19,4 @@ export interface ErrorHandling { */ updateAlive(error: Error): void; } -/** - * @since 2.0.0 - */ -export class DefaultErrorHandling implements ErrorHandling { - keepAlive = 5; - crash(error: Error): never { - throw error; - } - updateAlive(_: Error) { - this.keepAlive--; - } -} - diff --git a/src/core/contracts/index.ts b/src/core/contracts/index.ts index a9bb82f..649bab1 100644 --- a/src/core/contracts/index.ts +++ b/src/core/contracts/index.ts @@ -1,3 +1,3 @@ -export { type ErrorHandling, DefaultErrorHandling } from './error-handling'; -export { type Logging, DefaultLogging } from './logging'; -export { type ModuleManager, DefaultModuleManager } from './module-manager'; +export { type ErrorHandling } from './error-handling'; +export type { Logging, LogPayload } from './logging'; +export { type ModuleManager } from './module-manager'; diff --git a/src/core/contracts/logging.ts b/src/core/contracts/logging.ts index 43ca0b2..9ceb761 100644 --- a/src/core/contracts/logging.ts +++ b/src/core/contracts/logging.ts @@ -1,4 +1,3 @@ -import type { LogPayload } from '../../types/core'; /** * @since 2.0.0 */ @@ -8,24 +7,5 @@ export interface Logging { info(payload: LogPayload): void; debug(payload: LogPayload): void; } -/** - * @since 2.0.0 - */ -export class DefaultLogging implements Logging { - private date = () => new Date(); - debug(payload: LogPayload): void { - console.debug(`DEBUG: ${this.date().toISOString()} -> ${payload.message}`); - } - error(payload: LogPayload): void { - console.error(`ERROR: ${this.date().toISOString()} -> ${payload.message}`); - } - - info(payload: LogPayload): void { - console.info(`INFO: ${this.date().toISOString()} -> ${payload.message}`); - } - - warning(payload: LogPayload): void { - console.warn(`WARN: ${this.date().toISOString()} -> ${payload.message}`); - } -} +export type LogPayload = { message: T }; diff --git a/src/core/contracts/module-manager.ts b/src/core/contracts/module-manager.ts index 930406a..d856c2a 100644 --- a/src/core/contracts/module-manager.ts +++ b/src/core/contracts/module-manager.ts @@ -1,6 +1,5 @@ -import { ModuleStore } from '../../types/core'; -import { CommandModule } from '../../types/module'; -import { importModule } from '../module-loading'; +import { CommandModule } from "../types/modules"; + /** * @since 2.0.0 */ @@ -10,30 +9,4 @@ export interface ModuleManager { getPublishableCommands(): Promise; remove(id: string) : boolean } -/** - * @since 2.0.0 - */ -export class DefaultModuleManager implements ModuleManager { - constructor(private moduleStore: ModuleStore) {} - remove(id: string): boolean { - throw new Error('Method not implemented.'); - } - - get(id: string) { - return this.moduleStore.get(id); - } - set(id: string, path: string): void { - this.moduleStore.set(id, path); - } - //not tested - getPublishableCommands(): Promise { - const entries = this.moduleStore.entries(); - const publishable = 0b000000110; - return Promise.all( - Array.from(entries) - .filter(([id]) => (Number.parseInt(id.at(-1)!) & publishable) !== 0) - .map(([, path]) => importModule(path)), - ); - } -} diff --git a/src/core/create-plugins.ts b/src/core/create-plugins.ts index f4d710c..c6c721e 100644 --- a/src/core/create-plugins.ts +++ b/src/core/create-plugins.ts @@ -1,5 +1,5 @@ import { CommandType, EventType, PluginType } from './structures'; -import type { Plugin, PluginResult, EventArgs, CommandArgs } from '../types/plugin'; +import type { Plugin, PluginResult, EventArgs, CommandArgs } from './types/plugins'; import type { ClientEvents } from 'discord.js'; export function makePlugin( diff --git a/src/core/dependencies.ts b/src/core/dependencies.ts deleted file mode 100644 index 5219825..0000000 --- a/src/core/dependencies.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Container } from 'iti'; -import type { Dependencies, DependencyConfiguration, MapDeps, Wrapper } from '../types/core'; -import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from './contracts'; -import { SernEmitter } from './structures'; -import { SernError } from './structures/errors'; -import * as assert from 'node:assert' -import * as types from 'node:util/types' -import { Awaitable } from '../types/handler'; -export let containerSubject: Container<{}, {}>; -const requiredDependencyKeys = ['@sern/emitter', '@sern/errors', '@sern/logger'] as const; -/** - * @__PURE__ - * @since 2.0.0. - * use single if you want a singleton, or an object that is called once. - * @param cb - */ -export function single(cb: () => T) { - return cb; -} - -/** - * @__PURE__ - * @since 2.0.0 - * 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; -} -/** - * 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 async function composeRoot(conf: DependencyConfiguration) { - //container should have no client or logger yet. - const excludeLogger = conf.exclude?.has('@sern/logger'); - if (!excludeLogger) { - containerSubject.add({ - '@sern/logger': () => new DefaultLogging(), - }); - } - //Build the container based on the callback provided by the user - const updatedContainer = await conf.build(containerSubject as Container, {}>); - try { - updatedContainer.get('@sern/client'); - } catch { - throw new Error(SernError.MissingRequired + " No client was provided") - } - - if (!excludeLogger) { - updatedContainer.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); - } -} - -export function useContainer() { - console.warn(`Warning: using a container hook is not recommended. Could lead to many unwanted side effects`); - return (...keys: [...V]) => - keys.map(key => (containerSubject as Container).get(key)) as MapDeps; -} - -/** - * Returns the underlying data structure holding all dependencies. - * Exposes methods from iti - */ -export function useContainerRaw() { - assert.ok( - containerSubject && (containerSubject as CoreContainer).isReady(), - "Could not find container or container wasn't ready. Did you call makeDependencies?" - ); - return containerSubject; -} - -/** - * @since 2.0.0 - * @param conf a configuration for creating your project dependencies - */ -export async function makeDependencies( - conf: DependencyConfiguration, -) { - containerSubject = new CoreContainer(); - //Until there are more optional dependencies, just check if the logger exists - await composeRoot(conf); - (containerSubject as CoreContainer).ready(); - - return useContainer(); -} - -export interface Init { - init() : Awaitable -} - -/** - * Provides all the defaults for sern to function properly. - * The only user provided dependency needs to be @sern/client - */ -class CoreContainer extends Container { - private _ready = false; - constructor() { - super(); - (this as Container<{}, {}>) - .add({ - '@sern/errors': () => new DefaultErrorHandling(), - '@sern/store': () => new Map(), - '@sern/emitter': () => new SernEmitter() - }) - .add(ctx => { - return { '@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']) }; - }) - } - - async withInit(...keys: Keys[]) { - if(this.isReady()) { - throw Error("You cannot call this method after sern has started"); - } - for await (const k of keys) { - const dep = this.get(k); - assert.ok(dep !== undefined); - if('init' in dep && typeof dep.init === 'function') { - types.isAsyncFunction(dep.init) - ? await dep.init() - : dep.init() - } else { - throw Error(`called withInit with key ${k} but found nothing to init`) - } - } - return this; - } - isReady() { - return this._ready; - } - ready() { - this._ready = true; - } -} - - -/** - * A way for sern to grab only the necessary dependencies. - * Returns a function which allows for the user to call for more dependencies. - */ -export function makeFetcher( - containerConfig: Wrapper['containerConfig'], -) { - return (otherKeys: [...Keys]) => - containerConfig.get( - ...requiredDependencyKeys, - ...(otherKeys as (keyof Dependencies)[]), - ) as MapDeps; -} diff --git a/src/core/functions.ts b/src/core/functions.ts index f0f69e3..81dac3b 100644 --- a/src/core/functions.ts +++ b/src/core/functions.ts @@ -1,6 +1,6 @@ import { Err, Ok } from 'ts-results-es'; import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; -import type { SernAutocompleteData, SernOptionsData } from '../types/module'; +import type { SernAutocompleteData, SernOptionsData } from './types/modules'; //function wrappers for empty ok / err export const ok = /* @__PURE__*/ () => Ok.EMPTY; diff --git a/src/core/index.ts b/src/core/index.ts index 9494c0f..1900d05 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,4 +1,34 @@ export * from './contracts'; export * from './create-plugins'; export * from './structures'; -export { single, transient, useContainerRaw, makeDependencies } from './dependencies'; +export * from './ioc'; +export type { + CommandModule, + EventModule, + BothCommand, + ContextMenuMsg, + ContextMenuUser, + SlashCommand, + TextCommand, + ButtonCommand, + StringSelectCommand, + MentionableSelectCommand, + UserSelectCommand, + ChannelSelectCommand, + RoleSelectCommand, + ModalSubmitCommand, + DiscordEventCommand, + SernEventCommand, + ExternalEventCommand, + CommandModuleDefs, + EventModuleDefs, + BaseOptions, + SernAutocompleteData +} from './types/modules'; +export type { + Controller, + PluginResult, + InitPlugin, + ControlPlugin, + Plugin +} from './types/plugins'; diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts new file mode 100644 index 0000000..aaad603 --- /dev/null +++ b/src/core/ioc/base.ts @@ -0,0 +1,39 @@ +import * as assert from "assert"; +import { composeRoot, useContainer } from "./dependency-injection"; +import { DependencyConfiguration, Dependencies } from "./types"; +import { CoreContainer } from "../structures/container"; + + +//SIDE EFFECT: GLOBAL DI +let containerSubject: CoreContainer>; + +/** + * Returns the underlying data structure holding all dependencies. + * Exposes methods from iti + */ +export function useContainerRaw() { + assert.ok( + containerSubject && containerSubject.isReady(), + "Could not find container or container wasn't ready. Did you call makeDependencies?" + ); + return containerSubject; +} + +/** + * @since 2.0.0 + * @param conf a configuration for creating your project dependencies + */ +export async function makeDependencies( + conf: DependencyConfiguration, +) { + //Until there are more optional dependencies, just check if the logger exists + //SIDE EFFECT + containerSubject = new CoreContainer() + await composeRoot(conf); + + //SIDE EFFECT + containerSubject.ready(); + + return useContainer(); +} + diff --git a/src/core/ioc/dependency-injection.ts b/src/core/ioc/dependency-injection.ts new file mode 100644 index 0000000..783e0ce --- /dev/null +++ b/src/core/ioc/dependency-injection.ts @@ -0,0 +1,78 @@ +import type { CoreDependencies, Dependencies, DependencyConfiguration, MapDeps, IntoDependencies } from './types'; +import { DefaultLogging } from '../structures'; +import { SernError } from '../structures/errors'; +import { useContainerRaw } from './base'; +import { CoreContainer } from '../structures/container'; + + +/** + * @__PURE__ + * @since 2.0.0. + * Creates a singleton object. + * @param cb + */ +export function single(cb: () => T) { + return cb; +} + +/** + * @__PURE__ + * @since 2.0.0 + * Creates a transient object + * @param cb + */ +export function transient(cb: () => () => T) { + return cb; +} + +export function Service(key: string): unknown +export function Service(key: T) { + return useContainerRaw().get(key)! +} + +export function Services(...keys: [...T]) { + const container = useContainerRaw(); + return keys.map(k => container.get(k)!) as IntoDependencies +} + +/** + * 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 async function composeRoot(conf: DependencyConfiguration) { + //container should have no client or logger yet. + const excludeLogger = conf.exclude?.has('@sern/logger'); + const container = useContainerRaw(); + if (!excludeLogger) { + container.upsert({ + '@sern/logger': () => new DefaultLogging(), + }); + } + //Build the container based on the callback provided by the user + const updatedContainer = await conf.build(container as CoreContainer); + try { + updatedContainer.get('@sern/client'); + } catch { + throw new Error(SernError.MissingRequired + " No client was provided") + } + + if (!excludeLogger) { + updatedContainer.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); + } +} + +export function useContainer() { + console.warn(` + Warning: using a container hook is not recommended. + Could lead to many unwanted side effects. + Use the new Service(s) api function instead. + ` + ); + return (...keys: [...V]) => + keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as MapDeps; +} + + + diff --git a/src/core/ioc/index.ts b/src/core/ioc/index.ts new file mode 100644 index 0000000..1d0c582 --- /dev/null +++ b/src/core/ioc/index.ts @@ -0,0 +1,3 @@ +export { useContainerRaw, makeDependencies } from './base'; +export { Service, Services, single, transient } from './dependency-injection'; +export type { Singleton, Transient } from './types' diff --git a/src/core/ioc/types.ts b/src/core/ioc/types.ts new file mode 100644 index 0000000..bd8f810 --- /dev/null +++ b/src/core/ioc/types.ts @@ -0,0 +1,47 @@ +import { Container, UnpackFunction } from "iti"; +import { Awaitable, ModuleStore } from "../../shared"; +import { ErrorHandling, Logging, ModuleManager } from "../contracts"; +import { SernEmitter } from "../"; +import EventEmitter from "node:events"; + +export type Singleton = () => T; +export type Transient = () => () => T; + + +export interface CoreDependencies { + '@sern/logger'?: Singleton; + '@sern/emitter': Singleton; + '@sern/store': Singleton; + '@sern/modules': Singleton; + '@sern/errors': Singleton; +} + +export interface Dependencies extends CoreDependencies { + '@sern/client': Singleton; +} + + +export type DependencyFromKey = Dependencies[T]; +export type IntoDependencies = { + [Index in keyof Tuple]: UnpackFunction&{}>; //Unpack and make NonNullable +} & { length: Tuple['length'] }; + +export interface DependencyConfiguration { + //@deprecated. Loggers will always be included in the future + exclude?: Set<'@sern/logger'>; + build: (root: Container) => Awaitable>; +} + +//To be removed in future +//prettier-ignore +export type MapDeps = T extends [ + infer First extends keyof Deps, + ...infer Rest extends readonly unknown[], +] + ? [ + UnpackFunction, + ...(MapDeps extends [never] ? [] : MapDeps), + ] + : [never]; + + diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index de86807..bcdd102 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -1,14 +1,15 @@ import { SernError } from './structures/errors'; import { type Result, Err, Ok } from 'ts-results-es'; -import { Processed } from '../types/core'; -import { Module } from '../types/module'; +import { Module } from './types/modules'; import * as assert from 'node:assert'; import util from 'node:util'; import { type Observable, from, mergeMap, ObservableInput } from 'rxjs'; import { readdir, stat } from 'fs/promises'; import { basename, join, resolve } from 'path'; +import { Processed } from '../handler/types'; export type ModuleResult = Promise, SernError>>; + export async function importModule(absPath: string) { /// #if MODE === 'esm' return import(absPath).then(i => i.default as T); diff --git a/src/core/operators.ts b/src/core/operators.ts index 64e9579..5ebdc86 100644 --- a/src/core/operators.ts +++ b/src/core/operators.ts @@ -17,12 +17,12 @@ import { share, switchMap, } from 'rxjs'; -import type { PluginResult, VoidResult } from '../types/plugin'; import { Result } from 'ts-results-es'; -import { Awaitable } from '../types/handler'; import { EventEmitter } from 'node:events'; import { ErrorHandling, Logging } from './contracts'; import util from 'node:util' +import { Awaitable } from '../shared'; +import { PluginResult, VoidResult } from './types/plugins'; /** * if {src} is true, mapTo V, else ignore * @param item diff --git a/src/core/structures/container.ts b/src/core/structures/container.ts new file mode 100644 index 0000000..1c3b474 --- /dev/null +++ b/src/core/structures/container.ts @@ -0,0 +1,45 @@ +import { Container } from "iti"; +import { DefaultErrorHandling, DefaultModuleManager, SernEmitter } from "../"; +import { isAsyncFunction} from "node:util/types"; +import * as assert from 'node:assert' +import { Dependencies } from "../ioc/types"; +/** + * Provides all the defaults for sern to function properly. + * The only user provided dependency needs to be @sern/client + */ +export class CoreContainer> extends Container { + private _ready = false; + constructor() { + super(); + (this as Container<{}, {}>) + .add({ + '@sern/errors': () => new DefaultErrorHandling(), + '@sern/emitter': () => new SernEmitter(), + '@sern/modules': () => new DefaultModuleManager(new Map()) + }) + } + + async withInit(...keys: Keys[]) { + if(this.isReady()) { + throw Error("You cannot call this method after sern has started"); + } + for await (const k of keys) { + const dep = this.get(k); + assert.ok(dep !== undefined); + if('init' in dep && typeof dep.init === 'function') { + isAsyncFunction(dep.init) + ? await dep.init() + : dep.init() + } else { + throw Error(`called withInit with key ${k} but found nothing to init`) + } + } + return this; + } + isReady() { + return this._ready; + } + ready() { + this._ready = true; + } +} diff --git a/src/core/structures/context.ts b/src/core/structures/context.ts index 6fbb5be..1acaf7f 100644 --- a/src/core/structures/context.ts +++ b/src/core/structures/context.ts @@ -10,8 +10,8 @@ import { } from 'discord.js'; import { CoreContext } from './core-context'; import { Result, Ok, Err } from 'ts-results-es'; -import { ReplyOptions } from '../../types/handler'; import * as assert from 'assert'; +import { ReplyOptions } from '../../shared'; /** * @since 1.0.0 diff --git a/src/core/structures/index.ts b/src/core/structures/index.ts index 913cfd6..b269be9 100644 --- a/src/core/structures/index.ts +++ b/src/core/structures/index.ts @@ -1,3 +1,4 @@ export * from './enums'; export * from './context'; export * from './sern-emitter'; +export * from './services' diff --git a/src/core/structures/sern-emitter.ts b/src/core/structures/sern-emitter.ts index c25a665..3acb54b 100644 --- a/src/core/structures/sern-emitter.ts +++ b/src/core/structures/sern-emitter.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'node:events'; -import type { Payload, SernEventsMapping } from '../../types/handler'; import { PayloadType } from '../../core/structures'; -import type { Module } from '../../types/module'; +import { Payload, SernEventsMapping } from '../../shared'; +import { Module } from '../types/modules'; /** * @since 1.0.0 diff --git a/src/core/structures/services/error-handling.ts b/src/core/structures/services/error-handling.ts new file mode 100644 index 0000000..cde0d4b --- /dev/null +++ b/src/core/structures/services/error-handling.ts @@ -0,0 +1,14 @@ +import { ErrorHandling } from "../../contracts"; + +/** + * @since 2.0.0 + */ +export class DefaultErrorHandling implements ErrorHandling { + keepAlive = 5; + crash(error: Error): never { + throw error; + } + updateAlive(_: Error) { + this.keepAlive--; + } +} diff --git a/src/core/structures/services/index.ts b/src/core/structures/services/index.ts new file mode 100644 index 0000000..3f1d4ab --- /dev/null +++ b/src/core/structures/services/index.ts @@ -0,0 +1,3 @@ +export * from './error-handling'; +export * from './logger'; +export * from './module-manager'; diff --git a/src/core/structures/services/logger.ts b/src/core/structures/services/logger.ts new file mode 100644 index 0000000..39056b3 --- /dev/null +++ b/src/core/structures/services/logger.ts @@ -0,0 +1,23 @@ +import { LogPayload, Logging } from "../../contracts"; + +/** + * @since 2.0.0 + */ +export class DefaultLogging implements Logging { + private date = () => new Date(); + debug(payload: LogPayload): void { + console.debug(`DEBUG: ${this.date().toISOString()} -> ${payload.message}`); + } + + error(payload: LogPayload): void { + console.error(`ERROR: ${this.date().toISOString()} -> ${payload.message}`); + } + + info(payload: LogPayload): void { + console.info(`INFO: ${this.date().toISOString()} -> ${payload.message}`); + } + + warning(payload: LogPayload): void { + console.warn(`WARN: ${this.date().toISOString()} -> ${payload.message}`); + } +} diff --git a/src/core/structures/services/module-manager.ts b/src/core/structures/services/module-manager.ts new file mode 100644 index 0000000..307c99f --- /dev/null +++ b/src/core/structures/services/module-manager.ts @@ -0,0 +1,32 @@ +import { ModuleStore } from "../../../shared"; +import { ModuleManager } from "../../contracts"; +import { importModule } from "../../module-loading"; +import { CommandModule } from "../../types/modules"; + +/** +* @since 2.0.0 +*/ +export class DefaultModuleManager implements ModuleManager { + constructor(private moduleStore: ModuleStore) {} + + remove(id: string): boolean { + throw new Error('Method not implemented.'); + } + + get(id: string) { + return this.moduleStore.get(id); + } + set(id: string, path: string): void { + this.moduleStore.set(id, path); + } + //not tested + getPublishableCommands(): Promise { + const entries = this.moduleStore.entries(); + const publishable = 0b000000110; + return Promise.all( + Array.from(entries) + .filter(([id]) => (Number.parseInt(id.at(-1)!) & publishable) !== 0) + .map(([, path]) => importModule(path)), + ); + } +} diff --git a/src/types/module.ts b/src/core/types/modules.ts similarity index 67% rename from src/types/module.ts rename to src/core/types/modules.ts index f365933..4a854ef 100644 --- a/src/types/module.ts +++ b/src/core/types/modules.ts @@ -23,22 +23,22 @@ import { UserContextMenuCommandInteraction, UserSelectMenuInteraction, } from 'discord.js'; -import { InitArgs } from './core'; -import { Args, Payload, SlashOptions } from '../types/handler'; -import { Context } from '../classic/context'; -import { Processed } from '../types/core'; -import { CommandType, PluginType } from '../core/structures/enums'; -import type { Awaitable, SernEventsMapping } from './handler'; -import type { InitPlugin, ControlPlugin } from './plugin'; -import { EventType } from '../core/structures/enums'; -import type { AnyCommandPlugin, AnyEventPlugin } from './plugin'; -import { sernMeta } from '../commands'; +import { CommandType, Context, EventType } from '../structures'; +import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './plugins'; +import { Awaitable, SernEventsMapping } from '../../shared'; +import { sernMeta } from '../../handler/commands'; +import { Processed } from '../../handler/types'; +import { Args, SlashOptions } from '../../shared'; + + interface CommandMeta { fullPath: string; id: string; } +export type AnyDefinedModule = Processed; + export interface Module { type: CommandType | EventType; name?: string; @@ -48,12 +48,7 @@ export interface Module { [sernMeta]: CommandMeta; execute: (...args: any[]) => Awaitable; } -export interface CommandTypeModule extends Module { - type: CommandType; -} -export interface EventTypeModule extends Module { - type: EventType; -} + export interface SernEventCommand extends Module { name?: T; @@ -144,71 +139,7 @@ export interface BothCommand extends Module { options?: SernOptionsData[]; execute: (ctx: Context, args: Args) => Awaitable; } -export interface CommandArgsMatrix { - [CommandType.Text]: { - [PluginType.Control]: [Context, ['text', string[]]]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Slash]: { - [PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Both]: { - [PluginType.Control]: [Context, 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]: [InitArgs>]; - }; -} -export interface 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]: [InitArgs>]; - }; -} export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand; export type CommandModule = | TextCommand diff --git a/src/core/types/plugins.ts b/src/core/types/plugins.ts new file mode 100644 index 0000000..27abc49 --- /dev/null +++ b/src/core/types/plugins.ts @@ -0,0 +1,119 @@ +/* + * 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 { Err, Ok, Result } from 'ts-results-es'; +import type { BothCommand, ButtonCommand, ChannelSelectCommand, CommandModule, ContextMenuMsg, ContextMenuUser, DiscordEventCommand, EventModule, ExternalEventCommand, MentionableSelectCommand, ModalSubmitCommand, RoleSelectCommand, SernEventCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand } from './modules'; +import { Args, Awaitable, Payload, SlashOptions } from '../../shared'; +import { CommandType, Context, EventType, PluginType } from '../structures'; +import { InitArgs, Processed } from '../../handler/types'; +import { ButtonInteraction, ChannelSelectMenuInteraction, ClientEvents, MentionableSelectMenuInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserContextMenuCommandInteraction, UserSelectMenuInteraction } from 'discord.js'; + +export type PluginResult = Awaitable; +export type VoidResult = Result; + +export interface Controller { + next: () => Ok; + stop: () => Err; +} +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>]>; + +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]; + +export interface CommandArgsMatrix { + [CommandType.Text]: { + [PluginType.Control]: [Context, ['text', string[]]]; + [PluginType.Init]: [InitArgs>]; + }; + [CommandType.Slash]: { + [PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]]; + [PluginType.Init]: [InitArgs>]; + }; + [CommandType.Both]: { + [PluginType.Control]: [Context, 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]: [InitArgs>]; + }; +} + +export interface 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]: [InitArgs>]; + }; +} diff --git a/src/commands.ts b/src/handler/commands.ts similarity index 91% rename from src/commands.ts rename to src/handler/commands.ts index 7894a09..ba1a3c2 100644 --- a/src/commands.ts +++ b/src/handler/commands.ts @@ -1,10 +1,10 @@ import { ClientEvents } from 'discord.js'; -import { CommandType, EventType, PluginType } from './core/structures'; -import { AnyEventPlugin, Plugin } from './types/plugin'; -import { CommandModule, EventModule, InputCommand, InputEvent } from './types/module'; -import { partition } from './core/functions'; -import { filename, filePath } from './core/module-loading'; -import { Awaitable } from './types/handler'; +import { CommandType, EventType, PluginType } from '../core/structures'; +import { AnyEventPlugin, Plugin } from '../core/types/plugins'; +import { CommandModule, EventModule, InputCommand, InputEvent } from '../core/types/modules'; +import { partition } from '../core/functions'; +import { filename, filePath } from '../core/module-loading'; +import { Awaitable } from '../shared'; export const sernMeta = Symbol('@sern/meta'); const appBitField = 0b000000011111; /* diff --git a/src/handler/events/dispatchers.ts b/src/handler/events/dispatchers.ts index c0baf3f..261339d 100644 --- a/src/handler/events/dispatchers.ts +++ b/src/handler/events/dispatchers.ts @@ -1,5 +1,3 @@ -import type { Processed } from '../../types/core'; -import type { BothCommand, CommandModule, EventModule, Module } from '../../types/module'; import { EventEmitter } from 'node:events'; import * as assert from 'node:assert'; import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; @@ -8,9 +6,10 @@ import { createResultResolver } from './generic'; import { AutocompleteInteraction, BaseInteraction, Message } from 'discord.js'; import { treeSearch } from '../../core/functions'; import { SernError } from '../../core/structures/errors'; -import { Args } from '../../types/handler'; -import { CommandType, Context, EventType } from '../../core'; +import { CommandType, Context } from '../../core'; import { isAutocomplete } from '../../core/predicates'; +import { Args, Processed } from '../types'; +import { BothCommand, CommandModule, Module } from '../../core/types/modules'; export function dispatchInteraction< T extends CommandModule, @@ -101,7 +100,7 @@ export function createDispatcher(payload: { }) { switch (payload.module.type) { case CommandType.Text: - throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream.'); + throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream. ' + payload.module); case CommandType.Slash: case CommandType.Both: { if (isAutocomplete(payload.event)) { @@ -109,7 +108,7 @@ export function createDispatcher(payload: { * Autocomplete is a special case that * must be handled separately, since it's * too different from regular command modules - * cast safety: payload is already guaranteed to be a slash command or both command + * CAST SAFETY: payload is already guaranteed to be a slash command or both command */ return dispatchAutocomplete(payload as never); } diff --git a/src/handler/events/generic.ts b/src/handler/events/generic.ts index 591d642..3bed1bf 100644 --- a/src/handler/events/generic.ts +++ b/src/handler/events/generic.ts @@ -8,19 +8,18 @@ import { ModuleManager } from '../../core'; import { SernError } from '../../core/structures/errors'; import { callPlugin, everyPluginOk, filterMap, filterMapTo } from '../../core/operators'; import { defaultModuleLoader } from '../../core/module-loading'; -import { ImportPayload, Processed } from '../../types/core'; -import { CommandModule, Module } from '../../types/module'; +import { CommandModule, Module, AnyModule } from '../../core/types/modules'; import { contextArgs, createDispatcher, dispatchMessage } from './dispatchers'; import { ObservableInput, pipe, switchMap } from 'rxjs'; import { SernEmitter } from '../../core'; import { errTap } from '../../core/operators'; import * as Files from '../../core/module-loading'; -import { sernMeta } from '../../commands'; -import { AnyModule } from '../../types/module'; +import { sernMeta } from '../commands'; import { Err, Result } from 'ts-results-es'; -import { Awaitable } from '../../types/handler'; import { fmt } from './messages'; -import { ControlPlugin, VoidResult } from '../../types/plugin'; +import { ControlPlugin, VoidResult } from '../../core/types/plugins'; +import { ImportPayload, Processed } from '../types'; +import { Awaitable } from '../../shared'; function createGenericHandler( source: Observable, diff --git a/src/handler/events/interactions.ts b/src/handler/events/interactions.ts index 356f44d..a03d9a6 100644 --- a/src/handler/events/interactions.ts +++ b/src/handler/events/interactions.ts @@ -5,9 +5,10 @@ import { SernEmitter } from '../../core'; import { sharedObservable } from '../../core/operators'; import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates'; import { createInteractionHandler, executeModule, makeModuleExecutor } from './generic'; -import { DependencyList } from '../../types/core'; +import { DependencyList } from '../types'; + +export function makeInteractionHandler([emitter,,, modules, client]: DependencyList ) { -export function makeInteractionHandler([emitter, _, _1, modules, client]: DependencyList ) { const interactionStream$ = sharedObservable(client, 'interactionCreate'); const handle = createInteractionHandler(interactionStream$, modules); @@ -22,6 +23,6 @@ export function makeInteractionHandler([emitter, _, _1, modules, client]: Depend makeModuleExecutor(module => { emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); }), - concatMap(module => executeModule(emitter, module)), + concatMap(payload => executeModule(emitter, payload)), ); } diff --git a/src/handler/events/messages.ts b/src/handler/events/messages.ts index 58eabec..a3e683e 100644 --- a/src/handler/events/messages.ts +++ b/src/handler/events/messages.ts @@ -4,7 +4,7 @@ import type { Message } from 'discord.js'; import { SernEmitter } from '../../core'; import { sharedObservable } from '../../core/operators'; import { createMessageHandler, executeModule, isNonBot, makeModuleExecutor } from './generic'; -import { DependencyList } from '../../types/core'; +import { DependencyList } from '../types'; /** * Removes the first character(s) _[depending on prefix length]_ of the message @@ -21,7 +21,7 @@ export function fmt(msg: string, prefix: string): string[] { } export function makeMessageHandler( - [emitter, , log, modules, client]: DependencyList, + [emitter,, log, modules, client]: DependencyList, defaultPrefix: string | undefined, ) { if (!defaultPrefix) { diff --git a/src/handler/events/ready.ts b/src/handler/events/ready.ts index eaa9245..fdab30a 100644 --- a/src/handler/events/ready.ts +++ b/src/handler/events/ready.ts @@ -3,15 +3,15 @@ import { CommandType } from '../../core/structures'; import { SernError } from '../../core/structures/errors'; import { Result } from 'ts-results-es'; import { ModuleManager } from '../../core/contracts'; -import { SernEmitter, } from '../../core'; -import { sernMeta } from '../../commands'; -import { Processed, DependencyList } from '../../types/core'; -import { Module } from '../../types/module'; +import { SernEmitter } from '../../core'; +import { sernMeta } from '../commands'; +import { Processed, DependencyList } from '../types'; import * as assert from 'node:assert'; import { buildModules, callInitPlugins } from './generic'; +import { Module } from '../../core/types/modules'; export function startReadyEvent( - [sEmitter, errorHandler, , moduleManager, client]: DependencyList, + [sEmitter,,, moduleManager, client]: DependencyList, allPaths: ObservableInput, ) { const ready$ = fromEvent(client!, 'ready').pipe(take(1)); @@ -32,9 +32,9 @@ export function startReadyEvent( }), ) .subscribe(module => { - const result = registerModule(moduleManager, module as Processed); + const result = registerModule(moduleManager, module); if (result.err) { - errorHandler.crash(Error(SernError.InvalidModuleType)); + throw Error(SernError.InvalidModuleType); } }); } @@ -44,7 +44,9 @@ function registerModule>( module: T, ): Result { const { id, fullPath } = module[sernMeta]; - if (module.type === CommandType.Both || module.type === CommandType.Text) { + if (module.type === CommandType.Both + || module.type === CommandType.Text + ) { assert.ok('alias' in module); assert.ok(Array.isArray(module.alias)); module.alias?.forEach(a => manager.set(`${a}__A0`, fullPath)); diff --git a/src/handler/events/user-defined.ts b/src/handler/events/user-defined.ts index 326bd4a..55f3ed9 100644 --- a/src/handler/events/user-defined.ts +++ b/src/handler/events/user-defined.ts @@ -1,32 +1,30 @@ import { ObservableInput, catchError, finalize, map, mergeAll, of } from 'rxjs'; -import type { Dependencies, Processed, Wrapper } from '../../types/core'; import type { CommandModule, EventModule } from '../../types/module'; -import type { EventEmitter } from 'node:events'; import { SernEmitter } from '../../core'; -import type { ErrorHandling, Logging } from '../../core/contracts'; import { EventType } from '../../core/structures'; import { SernError } from '../../core/structures/errors'; import { eventDispatcher } from './dispatchers'; -import { handleError } from '../../core/contracts/error-handling'; -import { useContainerRaw } from '../../core/dependencies'; import { buildModules, callInitPlugins } from './generic'; +import { handleError } from '../../core/operators'; +import { Service, useContainerRaw } from '../../core/ioc'; +import { DependencyList, Processed } from '../types'; export function makeEventsHandler( - [s, err, log, client]: [SernEmitter, ErrorHandling, Logging | undefined, EventEmitter], + [emitter, err, log,, client]: DependencyList, allPaths: ObservableInput, - containerGetter: Wrapper['containerConfig'], ) { - const lazy = (k: string) => containerGetter.get(k as keyof Dependencies)[0]; + + //code smell const intoDispatcher = (e: Processed) => { switch (e.type) { case EventType.Sern: - return eventDispatcher(e, s); + return eventDispatcher(e, emitter); case EventType.Discord: return eventDispatcher(e, client); case EventType.External: - return eventDispatcher(e, lazy(e.emitter)); + return eventDispatcher(e, Service(e.emitter)); default: return err.crash( Error(SernError.InvalidModuleType + ' while creating event handler'), @@ -35,12 +33,12 @@ export function makeEventsHandler( }; of(null) .pipe( - buildModules(allPaths, s), + buildModules(allPaths, emitter), callInitPlugins({ onStop: module => - s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), + emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), onNext: ({ module }) => { - s.emit('module.register', SernEmitter.success(module)); + emitter.emit('module.register', SernEmitter.success(module)); return module; }, }), diff --git a/src/handler/sern.ts b/src/handler/sern.ts index cac8e51..7c850c9 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -2,12 +2,13 @@ import { makeEventsHandler } from './events/user-defined'; import { makeInteractionHandler } from './events/interactions'; import { startReadyEvent } from './events/ready'; import { makeMessageHandler } from './events/messages'; -import { makeFetcher, makeDependencies, useContainerRaw } from '../core/dependencies'; import { err, ok } from '../core/functions'; -import { Wrapper } from '../types/core'; -import { getCommands } from '../core/module-loading'; +import { getFullPathTree } from '../core/module-loading'; import { catchError, finalize, merge } from 'rxjs'; -import { handleError } from '../core/contracts/error-handling'; +import { handleError } from '../core/operators'; +import { Services, useContainerRaw } from '../core/ioc'; +import { Wrapper } from '../shared'; + /** * @since 1.0.0 * @param wrapper Options to pass into sern. @@ -23,20 +24,20 @@ import { handleError } from '../core/contracts/error-handling'; * }) * ``` */ + export function init(wrapper: Wrapper) { const startTime = performance.now(); - const dependenciesAnd = makeFetcher(wrapper.containerConfig); - const dependencies = dependenciesAnd(['@sern/modules', '@sern/client']); + + const dependencies = useDependencies(); if (wrapper.events !== undefined) { makeEventsHandler( - dependenciesAnd(['@sern/client']), - wrapper.events, - wrapper.containerConfig, + dependencies, + getFullPathTree(wrapper.events), ); } - startReadyEvent(dependencies, getCommands(wrapper.commands)).add(() => console.log('ready')); + startReadyEvent(dependencies, getFullPathTree(wrapper.commands)).add(() => console.log('ready')); const logger = dependencies[2]; const errorHandler = dependencies[1]; @@ -50,27 +51,28 @@ export function init(wrapper: Wrapper) { ).pipe( catchError(handleError(errorHandler, logger)), finalize(() => { - logger?.info({ message: 'a stream closed or reached end of lifetime' }); + logger?.info({ message: 'A stream closed or reached end of lifetime' }); useContainerRaw() ?.disposeAll() .then(() => logger?.info({ message: 'Cleaning container and crashing' })); }) - ).subscribe() + ).subscribe(); const endTime = performance.now(); - dependencies[2]?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` }); + logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` }); } +function useDependencies() { + return Services( + '@sern/emitter', + '@sern/errors', + '@sern/logger', + '@sern/modules', + '@sern/client' + ); +} -/** - * @deprecated - Please import the function directly: - * ```ts - * import { makeDependencies } from '@sern/handler' - * - * ``` - */ -export { makeDependencies }; /** * @since 1.0.0 * The object passed into every plugin to control a command's behavior diff --git a/src/handler/types.ts b/src/handler/types.ts new file mode 100644 index 0000000..9919359 --- /dev/null +++ b/src/handler/types.ts @@ -0,0 +1,23 @@ +import { ErrorHandling, Logging, ModuleManager, SernEmitter } from "../core"; +import EventEmitter from "node:events"; +import { Module } from "../core/types/modules"; + +export type Processed = T & { name: string; description: string }; + +export type DependencyList = [ + SernEmitter, + ErrorHandling, + Logging | undefined, + ModuleManager, + EventEmitter, +]; + +export interface InitArgs> { + module: T; + absPath: string; +} + +export interface ImportPayload { + module: T; + absPath: string; +} diff --git a/src/index.ts b/src/index.ts index 6acb213..5b7dda1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,5 @@ export * as Sern from './handler/sern'; -export * from './types/core'; -export * from './types/handler'; -export * from './types/module'; -export * from './types/plugin'; export * from './core'; +export { commandModule, eventModule, discordEvent } from './handler/commands' export { controller } from './handler/sern'; -export { commandModule, eventModule, discordEvent } from './commands'; diff --git a/src/types/handler.ts b/src/shared.ts similarity index 68% rename from src/types/handler.ts rename to src/shared.ts index 53b39c1..f05e8b9 100644 --- a/src/types/handler.ts +++ b/src/shared.ts @@ -1,29 +1,17 @@ import type { + CommandInteractionOptionResolver, InteractionReplyOptions, MessageReplyOptions, - CommandInteractionOptionResolver, } from 'discord.js'; -import { Processed } from './core'; -import { AnyModule, CommandModule, EventModule } from './module'; -import { PayloadType } from '../core'; - -export type Awaitable = PromiseLike | T; - -// Thanks to @kelsny -export type ParseType = { - [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; -}[keyof T]; - -export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; - -export type SlashOptions = Omit; +import { PayloadType } from './core'; +import { Dependencies } from './core/ioc/types'; +import { AnyModule } from './core/types/modules'; export type ReplyOptions = | string | Omit | MessageReplyOptions; -export type AnyDefinedModule = Processed; export type Payload = | { type: PayloadType.Success; module: AnyModule } | { type: PayloadType.Failure; module?: AnyModule; reason: string | Error } @@ -35,3 +23,31 @@ export interface SernEventsMapping { error: [Payload]; warning: [Payload]; } + + +export type Awaitable = PromiseLike | T; + +export type ModuleStore = Map; + +export type Deprecated = [never, Message]; + + +export interface Wrapper { + commands: string; + defaultPrefix?: string; + events?: string; + containerConfig?: { + get: (...keys: (keyof Dependencies)[]) => unknown[]; + }; +} + + +// Thanks to @kelsny +export type ParseType = { + [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; +}[keyof T]; + +export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; + +export type SlashOptions = Omit; + diff --git a/src/types/core.ts b/src/types/core.ts deleted file mode 100644 index 16d5dc0..0000000 --- a/src/types/core.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Core typings. - * Includes iti, dependencies, and other commonly used types - * Should not have discord.js imports - */ -import { type EventEmitter } from 'node:events'; -import { ErrorHandling, Logging, ModuleManager, SernEmitter } from '../core'; -import { Container, UnpackFunction } from 'iti'; -import { Module } from './module'; -import { Awaitable } from './handler'; - -export type ModuleStore = Map; -export type DependencyList = [ - SernEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, - EventEmitter, -]; -/** - * After modules are transformed, name and description are given default values if none - * are provided to Module. This type represents that transformation - */ - -export type LogPayload = { message: T }; -export type Singleton = () => T; -export type Transient = () => () => T; - -export interface CoreDependencies { - '@sern/logger'?: Singleton; - '@sern/emitter': Singleton; - '@sern/store': Singleton; - '@sern/modules': Singleton; - '@sern/errors': Singleton; -} - -export interface Dependencies extends CoreDependencies { - '@sern/client': Singleton; -} - -//prettier-ignore -export type MapDeps = T extends [ - infer First extends keyof Deps, - ...infer Rest extends readonly unknown[], -] - ? [ - UnpackFunction, - ...(MapDeps extends [never] ? [] : MapDeps), - ] - : [never]; - -/* - * @deprecated - * Will remove optional logger in the future - */export type OptionalDependencies = '@sern/logger'; -export type Processed = T & { name: string; description: string }; -export type Deprecated = [never, Message]; -export interface DependencyConfiguration { - //@deprecated. Loggers will always be included in the future - exclude?: Set; - build: (root: Container) => Awaitable>; -} - -export interface ImportPayload { - module: T; - absPath: string; -} - -export interface Wrapper { - commands: string; - defaultPrefix?: string; - events?: string; - containerConfig: { - get: (...keys: (keyof Dependencies)[]) => unknown[]; - }; -} -export interface InitArgs> { - module: T; - absPath: string; -} diff --git a/src/types/plugin.ts b/src/types/plugin.ts deleted file mode 100644 index 0351e72..0000000 --- a/src/types/plugin.ts +++ /dev/null @@ -1,52 +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 { Err, Ok, Result } from 'ts-results-es'; -import type { CommandType, EventType, PluginType } from '../core/structures'; -import type { CommandArgsMatrix, CommandModule, EventArgsMatrix, EventModule } from './module'; -import type { InitArgs } from './core'; -import type { Awaitable } from './handler'; -import { Processed } from './core'; -export type PluginResult = Awaitable; -export type VoidResult = Result; - -export interface Controller { - next: () => Ok; - stop: () => Err; -} -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>]>; - -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/tsup.config.js b/tsup.config.js index f9e89cf..d1ba7ca 100644 --- a/tsup.config.js +++ b/tsup.config.js @@ -19,7 +19,8 @@ export default defineConfig([ target: 'node16', tsconfig: './tsconfig-esm.json', outDir: './dist/esm', - splitting: false, + splitting: true, + bundle: true, esbuildPlugins: [ifdefPlugin({ variables: { MODE: 'esm' }, verbose: true })], outExtension() { return { @@ -50,5 +51,9 @@ export default defineConfig([ }, ...shared, }, - +// { +// dts: true, +// entry: ['src/presets/*.ts' ] +// +// } ]); diff --git a/yarn.lock b/yarn.lock index ff13f9f..e7b9297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,13 +5,6 @@ __metadata: version: 6 cacheKey: 8 -"@cloudflare/workers-types@npm:^4.20230419.0": - version: 4.20230419.0 - resolution: "@cloudflare/workers-types@npm:4.20230419.0" - checksum: db1eb1e74029da8ee3eaed8c8b83e1bc3a10801d328ccfddf39258e5eb679ffb34776fab441f6ac66aa304bc3715dedce77083aed3d79b4825639fb24970c164 - languageName: node - linkType: hard - "@discordjs/builders@npm:^1.6.3": version: 1.6.3 resolution: "@discordjs/builders@npm:1.6.3" @@ -449,7 +442,6 @@ __metadata: version: 0.0.0-use.local resolution: "@sern/handler@workspace:." dependencies: - "@cloudflare/workers-types": ^4.20230419.0 "@types/node": ^18.15.11 "@typescript-eslint/eslint-plugin": 5.58.0 "@typescript-eslint/parser": 5.58.0