refactor/decoupling (#265)

* fix npm script for workflows

* filter lazy modules

* lift inline function for readability

* perf: use one instance of operator instead of creating instances

* chore: move fmt closer to call site

* refactor: inline function lifting and readability

* add import payload type

* refactor: remove redundant pipe for single function operators

* refactor: clearer naming for resultResolver

* refactor: no unused variable warning for updateAlive

* style: pretty

* refactor: remove redundant getter

* style: pretty

* fix: typescript needs explicit definition for defineAllFields

* add LazyPaths map

* chore: update tsup and typescript

* chore: revert lazy module work and work on decoupling core

* fix npm script for workflows

* chore: fix typings

* refactor: inline function `defineAllFields`

* docs: add @since annotation

* style: prettier

* docs: add since annotations

* fix: typings

* chore: update dependencies

* chore: remove unused import

* style: pretty

* merge on home pc

* refactor: use dependencies less

---------

Co-authored-by: jacoobes <jacobnguyend@gmail.com>
This commit is contained in:
Jacob Nguyen
2023-04-10 22:12:26 -05:00
committed by GitHub
parent 473be775f0
commit 94070d99e8
24 changed files with 2175 additions and 2920 deletions

View File

@@ -19,7 +19,7 @@
"format": "eslint src/**/*.ts --fix",
"build:dev": "tsup && tsup --dts-only --outDir dist",
"build:prod": "tsup --minify && tsup --dts-only --outDir dist",
"publish": "npm run build:prod && npm publish",
"publish": "npm run build:prod",
"pretty": "prettier --write ."
},
"keywords": [
@@ -36,17 +36,17 @@
"dependencies": {
"iti": "^0.6.0",
"rxjs": "^7.8.0",
"ts-results-es": "^3.5.0"
"ts-results-es": "^3.6.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"discord.js": "^14.8.0",
"discord.js": "^14.9.0",
"esbuild-ifdef": "^0.2.0",
"eslint": "8.30.0",
"prettier": "2.8.4",
"typescript": "4.9.5",
"tsup": "^6.6.3"
"tsup": "^6.7.0",
"typescript": "5.0.2"
},
"repository": {
"type": "git",

4672
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
import type { Observable } from 'rxjs';
import type { Logging } from './logging';
import util from 'util';
/**
* @since 2.0.0
*/
export interface ErrorHandling {
/**
* Number of times the process should throw an error until crashing and exiting
@@ -20,13 +22,15 @@ 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(e: Error) {
updateAlive(_: Error) {
this.keepAlive--;
}
}

View File

@@ -1,12 +1,16 @@
import type { LogPayload } from '../../types/handler';
/**
* @since 2.0.0
*/
export interface Logging<T = unknown> {
error(payload: LogPayload<T>): void;
warning(payload: LogPayload<T>): void;
info(payload: LogPayload<T>): void;
debug(payload: LogPayload<T>): void;
}
/**
* @since 2.0.0
*/
export class DefaultLogging implements Logging {
private date = () => new Date();
debug(payload: LogPayload): void {

View File

@@ -1,14 +1,18 @@
import type { CommandModuleDefs } from '../../types/module';
import type { CommandType, ModuleStore } from '../structures';
import type { Processed } from '../../types/handler';
/**
* @since 2.0.0
*/
export interface ModuleManager {
get<T extends CommandType>(
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
): Processed<CommandModuleDefs[T]> | undefined;
set(strat: (ms: ModuleStore) => void): void;
}
/**
* @since 2.0.0
*/
export class DefaultModuleManager implements ModuleManager {
constructor(private moduleStore: ModuleStore) {}
get<T extends CommandType>(

View File

@@ -18,11 +18,13 @@ type NotFunction =
export function single<T extends NotFunction>(cb: T): () => T;
/**
* New signature
* @since 2.0.0
* @param cb
*/
export function single<T extends () => unknown>(cb: T): T;
/**
* @__PURE__
* @since 2.0.0.
* 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!!**
@@ -41,6 +43,7 @@ export function transient<T extends NotFunction>(cb: T): () => () => T;
export function transient<T extends () => () => unknown>(cb: T): T;
/**
* @__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

View File

@@ -6,7 +6,7 @@ import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../
import { EventEmitter } from 'events';
import * as assert from 'assert';
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
import { callPlugin } from '../operators';
import { arrayifySource, callPlugin } from '../operators';
import { createResultResolver } from '../observableHandling';
export function dispatchCommand(module: Processed<CommandModule>, createArgs: () => unknown[]) {
@@ -17,6 +17,21 @@ export function dispatchCommand(module: Processed<CommandModule>, createArgs: ()
};
}
function intoPayload(module: Processed<Module>) {
return pipe(
arrayifySource,
map(args => ({ module, args })),
);
}
const createResult = createResultResolver<
Processed<Module>,
{ module: Processed<Module>; args: unknown[] },
unknown[]
>({
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
onNext: ({ args }) => args,
});
/**
* Creates an observable from { source }
* @param module
@@ -24,27 +39,15 @@ export function dispatchCommand(module: Processed<CommandModule>, createArgs: ()
*/
export function eventDispatcher(module: Processed<Module>, 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 arrayify = pipe(
map(event => (Array.isArray(event) ? (event as unknown[]) : [event])),
map(args => ({ module, args })),
const execute: OperatorFunction<unknown[], unknown> = concatMap(async args =>
module.execute(...args),
);
const createResult = createResultResolver<
Processed<Module>,
{ module: Processed<Module>; args: unknown[] },
unknown[]
>({
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
onSuccess: ({ args }) => args,
});
const execute: OperatorFunction<unknown[], unknown> = pipe(
concatMap(async args => module.execute(...args)),
return fromEvent(source, module.name).pipe(
intoPayload(module),
concatMap(createResult),
execute,
);
return fromEvent(source, module.name).pipe(arrayify, concatMap(createResult), execute);
}
export function dispatchAutocomplete(

View File

@@ -1,4 +1,4 @@
import type { Interaction } from 'discord.js';
import { Interaction } from 'discord.js';
import {
catchError,
concatMap,
@@ -32,9 +32,10 @@ function makeInteractionProcessor(
return pipe(
concatMap(event => {
if (event.isMessageComponent()) {
const module = get(ms =>
ms.InteractionHandlers[event.componentType].get(event.customId),
);
const customId = event.customId;
const module = get(ms => {
return ms.InteractionHandlers[event.componentType].get(customId);
});
return of({ module, event });
} else if (event.isCommand() || event.isAutocomplete()) {
const commandName = event.commandName;

View File

@@ -1,8 +1,7 @@
import { catchError, concatMap, EMPTY, finalize, fromEvent, map, of, pipe } from 'rxjs';
import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of, pipe } from 'rxjs';
import { 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, TextCommand } from '../../types/module';
import { ErrorHandling, handleError } from '../contracts/errorHandling';
import { contextArgs, dispatchCommand } from './dispatchers';
@@ -12,6 +11,20 @@ import { useContainerRaw } from '../dependencies';
import type { Logging, ModuleManager } from '../contracts';
import type { EventEmitter } from 'node:events';
/**
* Removes the first character(s) _[depending on prefix length]_ of the message
* @param msg
* @param prefix The prefix to remove
* @returns The message without the prefix
* @example
* message.content = '!ping';
* console.log(fmt(message, '!'));
* // [ 'ping' ]
*/
export function fmt(msg: string, prefix: string): string[] {
return msg.slice(prefix.length).trim().split(/\s+/g);
}
/**
* An operator function that processes a message to fetch a command module and prepares context payload.
* @param defaultPrefix
@@ -58,7 +71,7 @@ export function makeMessageCreate(
const get = (cb: (ms: ModuleStore) => Processed<CommandModule> | undefined) => {
return modules.get(cb);
};
const messageStream$ = fromEvent<Message>(client, 'messageCreate');
const messageStream$ = fromEvent(client, 'messageCreate') as Observable<Message>;
const messageProcessor = createMessageProcessor(defaultPrefix, get);
return messageStream$
.pipe(

View File

@@ -1,32 +1,25 @@
import type { Awaitable, Message } from 'discord.js';
import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs';
import { concatMap, EMPTY, filter, from, Observable, of, tap, throwError } from 'rxjs';
import { Result } from 'ts-results-es';
import type { CommandModule, EventModule, Module } from '../../types/module';
import SernEmitter from '../sernEmitter';
import { callPlugin, everyPluginOk, filterMapTo } from './operators';
import type { Processed } from '../../types/handler';
import type { ImportPayload, Processed } from '../../types/handler';
import type { ControlPlugin, VoidResult } from '../../types/plugin';
function hasPrefix(prefix: string, content: string) {
const prefixInContent = content.slice(0, prefix.length);
return prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0;
}
/**
* Ignores messages from any person / bot except itself
* @param prefix
*/
export function ignoreNonBot<T extends Message>(prefix: string) {
return (src: Observable<T>) =>
new Observable<T>(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);
}
},
});
});
export function ignoreNonBot(prefix: string) {
const messageFromHumanAndHasPrefix = ({ author, content }: Message) =>
!author.bot && hasPrefix(prefix, content);
return filter(messageFromHumanAndHasPrefix);
}
/**
@@ -75,8 +68,8 @@ export function createResultResolver<
Args extends { module: T; [key: string]: unknown },
Output,
>(config: {
onFailure?: (module: T) => unknown;
onSuccess: (args: Args) => Output;
onStop?: (module: T) => unknown;
onNext: (args: Args) => Output;
createStream: (args: Args) => Observable<VoidResult>;
}) {
return (args: Args) => {
@@ -84,49 +77,45 @@ export function createResultResolver<
return task$.pipe(
tap(result => {
if (result.err) {
config.onFailure?.(args.module);
config.onStop?.(args.module);
}
}),
everyPluginOk(),
filterMapTo(() => config.onSuccess(args)),
everyPluginOk,
filterMapTo(() => config.onNext(args)),
);
};
}
/**
* Calls a module's init plugins and checks for Err. If so, call { onFailure } and
* Calls a module's init plugins and checks for Err. If so, call { onStop } and
* ignore the module
*/
export function callInitPlugins<
T extends Processed<CommandModule | EventModule>,
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,
}),
),
Args extends ImportPayload<T>,
>(config: { onStop?: (module: T) => unknown; onNext: (module: Args) => T }) {
return 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
* @param onStop emits a failure response to the SernEmitter
*/
export function makeModuleExecutor<
M extends Processed<Module>,
Args extends { module: M; args: unknown[] },
>(onFailure: (m: M) => unknown) {
const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module });
return pipe(
concatMap(
createResultResolver({
onFailure,
createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)),
onSuccess,
}),
),
>(onStop: (m: M) => unknown) {
const onNext = ({ args, module }: Args) => ({ task: () => module.execute(...args), module });
return concatMap(
createResultResolver({
onStop,
createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)),
onNext,
}),
);
}

View File

@@ -10,15 +10,14 @@ import { nameOrFilename } from '../../utilities/functions';
import type { PluginResult, VoidResult } from '../../../types/plugin';
import { guayin } from '../../plugins';
import { controller } from '../../sern';
import { SernError } from '../../structures';
import { Result } from 'ts-results-es';
import { ImportPayload, Processed } from '../../../types/handler';
/**
* if {src} is true, mapTo V, else ignore
* @param item
*/
export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
return pipe(concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY)));
return concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY));
}
/**
@@ -31,35 +30,31 @@ export function callPlugin(args: unknown): OperatorFunction<
},
VoidResult
> {
return pipe(
concatMap(async plugin => {
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 concatMap(async plugin => {
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);
}
});
}
/**
* operator function that fill the defaults for a module
*/
export function defineAllFields<T extends AnyModule>() {
const fillFields = ({ module, absPath }: { module: T; absPath: string }) => ({
export const arrayifySource = map(src => (Array.isArray(src) ? (src as unknown[]) : [src]));
export const fillDefaults = <T extends AnyModule>({ module, absPath }: ImportPayload<T>) => {
return {
absPath,
module: {
name: nameOrFilename(module.name, absPath),
description: module.description ?? '...',
name: nameOrFilename(module?.name, absPath),
description: module?.description ?? '...',
...module,
},
});
return pipe(map(fillFields));
}
};
};
/**
* If the current value in Result stream is an error, calls callback.
@@ -67,30 +62,21 @@ export function defineAllFields<T extends AnyModule>() {
* @param cb
* @returns Observable<{ module: T; absPath: string }>
*/
export function errTap<T extends AnyModule>(
cb: (err: SernError) => void,
): OperatorFunction<
Result<{ module: T; absPath: string }, SernError>,
{ module: T; absPath: string }
> {
return pipe(
concatMap(result => {
if (result.ok) {
return of(result.val);
} else {
cb(result.val);
return EMPTY;
}
}),
);
export function errTap<Ok, Err>(cb: (err: Err) => void): OperatorFunction<Result<Ok, Err>, Ok> {
return concatMap(result => {
if (result.ok) {
return of(result.val);
} else {
cb(result.val as Err);
return EMPTY;
}
});
}
/**
* Checks if the stream of results is all ok.
*/
export function everyPluginOk(): OperatorFunction<VoidResult, boolean> {
return pipe(
every(result => result.ok),
defaultIfEmpty(true),
);
}
export const everyPluginOk: OperatorFunction<VoidResult, boolean> = pipe(
every(result => result.ok),
defaultIfEmpty(true),
);

View File

@@ -1,4 +1,4 @@
import { fromEvent, pipe, switchMap, take } from 'rxjs';
import { fromEvent, map, pipe, switchMap, take } from 'rxjs';
import * as Files from '../module-loading/readFile';
import { callInitPlugins } from './observableHandling';
import { CommandType, type ModuleStore, SernError } from '../structures';
@@ -8,17 +8,17 @@ import type { CommandModule } from '../../types/module';
import type { Processed } from '../../types/handler';
import type { ErrorHandling, Logging, ModuleManager } from '../contracts';
import { err, ok } from '../utilities/functions';
import { defineAllFields, errTap } from './operators';
import { errTap, fillDefaults } from './operators';
import SernEmitter from '../sernEmitter';
import type { EventEmitter } from 'node:events';
function buildCommandModules(commandDir: string, sernEmitter: SernEmitter) {
return pipe(
switchMap(() => Files.buildData<CommandModule>(commandDir)),
switchMap(() => Files.buildModuleStream<CommandModule>(commandDir)),
errTap(error => {
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
}),
defineAllFields(),
map(fillDefaults),
);
}
export function makeReadyEvent(
@@ -36,13 +36,13 @@ export function makeReadyEvent(
.pipe(
buildCommandModules(commandDir, sEmitter),
callInitPlugins({
onFailure: module => {
onStop: module => {
sEmitter.emit(
'module.register',
SernEmitter.failure(module, SernError.PluginFailure),
);
},
onSuccess: ({ module }) => {
onNext: ({ module }) => {
sEmitter.emit('module.register', SernEmitter.success(module));
return module;
},

View File

@@ -1,5 +1,5 @@
import { catchError, finalize, map, mergeAll } from 'rxjs';
import { buildData } from '../module-loading/readFile';
import * as Files from '../module-loading/readFile';
import type { Dependencies, Processed } from '../../types/handler';
import { callInitPlugins } from './observableHandling';
import type { CommandModule, EventModule } from '../../types/module';
@@ -9,7 +9,7 @@ import type { ErrorHandling, Logging } from '../contracts';
import { SernError, EventType, type Wrapper } from '../structures';
import { eventDispatcher } from './dispatchers';
import { handleError } from '../contracts/errorHandling';
import { defineAllFields, errTap } from './operators';
import { errTap, fillDefaults } from './operators';
import { useContainerRaw } from '../dependencies';
export function makeEventsHandler(
@@ -21,11 +21,11 @@ export function makeEventsHandler(
const eventStream$ = eventObservable(eventsPath, s);
const eventCreation$ = eventStream$.pipe(
defineAllFields(),
map(fillDefaults),
callInitPlugins({
onFailure: module =>
onStop: module =>
s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
onSuccess: ({ module }) => {
onNext: ({ module }) => {
s.emit('module.register', SernEmitter.success(module));
return module;
},
@@ -40,7 +40,9 @@ export function makeEventsHandler(
case EventType.External:
return eventDispatcher(e, lazy(e.emitter));
default:
err.crash(Error(SernError.InvalidModuleType + ' while creating event handler'));
return err.crash(
Error(SernError.InvalidModuleType + ' while creating event handler'),
);
}
};
eventCreation$
@@ -64,7 +66,7 @@ export function makeEventsHandler(
}
function eventObservable(events: string, emitter: SernEmitter) {
return buildData<EventModule>(events).pipe(
return Files.buildModuleStream<EventModule>(events).pipe(
errTap(reason => {
emitter.emit('module.register', SernEmitter.failure(undefined, reason));
}),

View File

@@ -1,8 +1,10 @@
import { readdirSync, statSync } from 'fs';
import { join } from 'path';
import { type Observable, from, mergeAll } from 'rxjs';
import { type Observable, from, mergeMap } from 'rxjs';
import { SernError } from '../structures/errors';
import { type Result, Err, Ok } from 'ts-results-es';
import { ImportPayload } from '../../types/handler';
import { pathToFileURL } from 'node:url';
// Courtesy @Townsy45
function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
@@ -19,45 +21,44 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
return arrayOfFiles;
}
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
// export const isLazy = (n: string) => n.indexOf(".lazy.", n.length-9) !== -1;
export async function defaultModuleLoader<T>(
absPath: string,
): Promise<Result<ImportPayload<T>, SernError>> {
// prettier-ignore
let module: T | undefined
/// #if MODE === 'esm'
= (await import(pathToFileURL(absPath).toString())).default
/// #elif MODE === 'cjs'
= require(absPath).default; // eslint-disable-line
/// #endif
if (module === undefined) {
return Err(SernError.UndefinedModule);
}
try {
module = new (module as unknown as new () => T)();
} catch {}
return Ok({ module, absPath });
}
/**
* a directory string is converted into a stream of modules.
* starts the stream of modules that sern needs to process on init
* 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
*/
export function buildData<T>(commandDir: string): Observable<
Result<
{
module: T;
absPath: string;
},
SernError
>
> {
export function buildModuleStream<T>(
commandDir: string,
): Observable<Result<ImportPayload<T>, SernError>> {
const commands = getCommands(commandDir);
return from(
Promise.all(
commands.map(async absPath => {
// prettier-ignore
let module: T | undefined
/// #if MODE === 'esm'
= (await import(`file:///` + absPath)).default
/// #elif MODE === 'cjs'
= require(absPath).default; // eslint-disable-line
/// #endif
return from(commands).pipe(mergeMap(defaultModuleLoader<T>));
}
if (module === undefined) {
return Err(SernError.UndefinedModule);
}
try {
module = new (module as unknown as new () => T)();
} catch {}
return Ok({ module, absPath });
}),
),
).pipe(mergeAll());
export function fullPathFrom(dir: string) {
return join(process.cwd(), dir);
}
export function getCommands(dir: string): string[] {
return readPath(join(process.cwd(), dir));
return readPath(fullPathFrom(dir));
}

View File

@@ -13,25 +13,37 @@ export function makePlugin<V extends unknown[]>(
[guayin]: undefined,
} as Plugin<V>;
}
/**
* @since 2.5.0
*
*/
export function EventInitPlugin<I extends EventType>(
execute: (...args: EventArgs<I, PluginType.Init>) => PluginResult,
) {
return makePlugin(PluginType.Init, execute);
}
/**
* @since 2.5.0
*
*/
export function CommandInitPlugin<I extends CommandType>(
execute: (...args: CommandArgs<I, PluginType.Init>) => PluginResult,
) {
return makePlugin(PluginType.Init, execute);
}
/**
* @since 2.5.0
*
*/
export function CommandControlPlugin<I extends CommandType>(
execute: (...args: CommandArgs<I, PluginType.Control>) => PluginResult,
) {
return makePlugin(PluginType.Control, execute);
}
/**
* @since 2.5.0
*
*/
export function EventControlPlugin<I extends EventType>(
execute: (...args: EventArgs<I, PluginType.Control>) => PluginResult,
) {
@@ -39,6 +51,7 @@ export function EventControlPlugin<I extends EventType>(
}
/**
* @since 2.5.0
* @Experimental
* A specialized function for creating control plugins with discord.js ClientEvents.
* Will probably be moved one day!

View File

@@ -20,7 +20,7 @@ import { err, ok, partition } from './utilities/functions';
import type { Awaitable, ClientEvents } from 'discord.js';
/**
*
* @since 1.0.0
* @param wrapper Options to pass into sern.
* Function to start the handler up
* @example
@@ -43,14 +43,16 @@ export function init(wrapper: Wrapper) {
if (events !== undefined) {
makeEventsHandler(requiredDependenciesAnd([]), events, wrapper.containerConfig);
}
makeReadyEvent(requiredDependenciesAnd(['@sern/modules']), wrapper.commands);
makeMessageCreate(requiredDependenciesAnd(['@sern/modules']), wrapper.defaultPrefix);
makeInteractionCreate(requiredDependenciesAnd(['@sern/modules']));
const dependencies = requiredDependenciesAnd(['@sern/modules']);
makeReadyEvent(dependencies, wrapper.commands);
makeMessageCreate(dependencies, wrapper.defaultPrefix);
makeInteractionCreate(dependencies);
const endTime = performance.now();
logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
}
/**
* @since 1.0.0
* The object passed into every plugin to control a command's behavior
*/
export const controller = {
@@ -59,6 +61,7 @@ export const controller = {
};
/**
* @since 1.0.0
* The wrapper function to define command modules for sern
* @param mod
*/
@@ -74,6 +77,7 @@ export function commandModule(mod: InputCommand): CommandModule {
} as CommandModule;
}
/**
* @since 1.0.0
* The wrapper function to define event modules for sern
* @param mod
*/
@@ -103,6 +107,7 @@ export function discordEvent<T extends keyof ClientEvents>(mod: {
return eventModule({ type: EventType.Discord, ...mod });
}
/**
* @since 2.0.0
* @param conf a configuration for creating your project dependencies
*/
export function makeDependencies<T extends Dependencies>(conf: DependencyConfiguration<T>) {
@@ -121,7 +126,8 @@ export abstract class CommandExecutable<Type extends CommandType> {
onEvent: ControlPlugin[] = [];
abstract execute: CommandModuleDefs[Type]['execute'];
}
/**@Experimental
/**
* @Experimental
* Will be refactored in future
*/
export abstract class EventExecutable<Type extends EventType> {

View File

@@ -3,6 +3,9 @@ import type { Payload, SernEventsMapping } from '../types/handler';
import { PayloadType } from './structures';
import type { Module } from '../types/module';
/**
* @since 1.0.0
*/
class SernEmitter extends EventEmitter {
/**
* Listening to sern events with on. This event stays on until a crash or a normal exit

View File

@@ -15,6 +15,7 @@ function safeUnwrap<T>(res: Either<T, T>) {
return res.val;
}
/**
* @since 1.0.0
* Provides values shared between
* Message and ChatInputCommandInteraction
*/

View File

@@ -1,4 +1,5 @@
/**
* @since 1.0.0
* A bitfield that discriminates command modules
* @enum { number }
* @example

View File

@@ -3,6 +3,7 @@ import { ApplicationCommandType, ComponentType } from 'discord.js';
import type { Processed } from '../../types/handler';
/**
* @since 2.0.0
* Storing all command modules
* This dependency is usually injected into ModuleManager
*/

View File

@@ -1,6 +1,7 @@
import type { Dependencies } from '../../types/handler';
/**
* @since 1.0.0
* An object to be passed into Sern#init() function.
* @typedef {object} Wrapper
*/

View File

@@ -1,13 +0,0 @@
/**
* Removes the first character(s) _[depending on prefix length]_ of the message
* @param msg
* @param prefix The prefix to remove
* @returns The message without the prefix
* @example
* message.content = '!ping';
* console.log(fmt(message, '!'));
* // [ 'ping' ]
*/
export function fmt(msg: string, prefix: string): string[] {
return msg.slice(prefix.length).trim().split(/\s+/g);
}

View File

@@ -67,3 +67,5 @@ export interface DependencyConfiguration<T extends Dependencies> {
exclude?: Set<OptionalDependencies>;
build: (root: Container<Omit<Dependencies, '@sern/client'>, {}>) => Container<T, {}>;
}
export type ImportPayload<T> = { module: T; absPath: string };

View File

@@ -6,10 +6,10 @@
"noImplicitAny": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"importsNotUsedAsValues": "error",
"moduleResolution": "node",
"skipLibCheck": true,
"declaration": true,
"preserveSymlinks": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
},