documentation+clean

This commit is contained in:
Jacob Nguyen
2025-01-12 12:14:01 -06:00
parent b00c892611
commit bb80b24258
9 changed files with 121 additions and 28 deletions

View File

@@ -15,16 +15,107 @@ export function makePlugin<V extends unknown[]>(
export function EventInitPlugin(execute: (args: InitArgs) => PluginResult) {
return makePlugin(PluginType.Init, execute);
}
/**
* Creates an initialization plugin for command preprocessing and modification
*
* @since 2.5.0
* @template I - Extends CommandType to enforce type safety for command modules
*
* @param {function} execute - Function to execute during command initialization
* @param {InitArgs<T>} execute.args - The initialization arguments
* @param {T} execute.args.module - The command module being initialized
* @param {string} execute.args.absPath - The absolute path to the module file
* @param {Dependencies} execute.args.deps - Dependency injection container
*
* @returns {Plugin} A plugin that runs during command initialization
*
* @example
* // Plugin to update command description
* export const updateDescription = (description: string) => {
* return CommandInitPlugin(({ deps }) => {
* if(description.length > 100) {
* deps.logger?.info({ message: "Invalid description" })
* return controller.stop("From updateDescription: description is invalid");
* }
* module.description = description;
* return controller.next();
* });
* };
*
* @example
* // Plugin to store registration date in module locals
* export const dateRegistered = () => {
* return CommandInitPlugin(({ module }) => {
* module.locals.registered = Date.now()
* return controller.next();
* });
* };
*
* @remarks
* - Init plugins can modify how commands are loaded and perform preprocessing
* - The module.locals object can be used to store custom plugin-specific data
* - Be careful when modifying module fields as multiple plugins may interact with them
* - Use controller.next() to continue to the next plugin
* - Use controller.stop(reason) to halt plugin execution
*/
export function CommandInitPlugin<I extends CommandType>(
execute: (args: InitArgs) => PluginResult
) {
): Plugin {
return makePlugin(PluginType.Init, execute);
}
/**
* Creates a control plugin for command preprocessing, filtering, and state management
*
* @since 2.5.0
* @template I - Extends CommandType to enforce type safety for command modules
*
* @param {function} execute - Function to execute during command control flow
* @param {CommandArgs<I>} execute.args - The command arguments array
* @param {Context} execute.args[0] - The discord context (e.g., guild, channel, user info, interaction)
* @param {SDT} execute.args[1] - The State, Dependencies, Params, Module, and Type object
*
* @returns {Plugin} A plugin that runs during command execution flow
*
* @example
* // Plugin to restrict command to specific guild
* export const inGuild = (guildId: string) => {
* return CommandControlPlugin((ctx, sdt) => {
* if(ctx.guild.id !== guildId) {
* return controller.stop();
* }
* return controller.next();
* });
* };
*
* @example
* // Plugins passing state through the chain
* const plugin1 = CommandControlPlugin((ctx, sdt) => {
* return controller.next({ 'plugin1/data': 'from plugin1' });
* });
*
* const plugin2 = CommandControlPlugin((ctx, sdt) => {
* return controller.next({ 'plugin2/data': ctx.user.id });
* });
*
* export default commandModule({
* type: CommandType.Slash,
* plugins: [plugin1, plugin2],
* execute: (ctx, sdt) => {
* console.log(sdt.state); // Access accumulated state
* }
* });
*
* @remarks
* - Control plugins are executed in order when a discord.js event is emitted
* - Use controller.next() to continue to next plugin or controller.stop() to halt execution
* - State can be passed between plugins using controller.next({ key: value })
* - State keys should be namespaced to avoid collisions (e.g., 'plugin-name/key')
* - Final accumulated state is passed to the command's execute function
* - All plugins must succeed for the command to execute
* - Plugins have access to dependencies through the sdt.deps object
* - Useful for implementing preconditions, filters, and command preprocessing
*/
export function CommandControlPlugin<I extends CommandType>(
execute: (...args: CommandArgs<I>) => PluginResult,

View File

@@ -1,10 +1,9 @@
import type { Emitter, Logging } from '../core/interfaces';
import type { Emitter } from '../core/interfaces';
import { SernError } from '../core/structures/enums'
import { Ok, wrapAsync} from '../core/structures/result';
import type { Module } from '../types/core-modules';
import { inspect } from 'node:util'
import { resultPayload, } from '../core/functions'
import { resultPayload } from '../core/functions'
import merge from 'deepmerge'

View File

@@ -2,7 +2,7 @@ import type { Module } from '../types/core-modules'
import { callPlugins, executeModule } from './event-utils';
import { SernError } from '../core/structures/enums'
import { createSDT, isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload, treeSearch } from '../core/functions'
import { UnpackedDependencies } from '../types/utility';
import type { UnpackedDependencies } from '../types/utility';
import * as Id from '../core/id'
import { Context } from '../core/structures/context';
@@ -41,22 +41,20 @@ export function interactionHandler(deps: UnpackedDependencies, defaultPrefix?: s
} else if (isModal(event) || isMessageComponent(event)) {
payload={ module, args: [event, createSDT(module, deps, params)] }
} else {
throw Error("Invalid event")
throw Error("Unknown interaction while handling in interactionCreate event " + event)
}
const result = await callPlugins(payload)
if(!result.ok) {
reporter.emit('module.activate', resultPayload('failure', module, result.error ?? SernError.PluginFailure))
return
}
if(payload.args.length != 2) {
if(payload.args.length !== 2) {
throw Error ('Invalid payload')
}
//@ts-ignore assigning final state from plugin
payload.args[1].state = result.value
// will be blocking if long task + await
// todo, add to task queue
// note: do not await this. will be blocking if long task (ie waiting for modal input)
executeModule(reporter, { module, args: payload.args });
});
}

View File

@@ -2,7 +2,7 @@ import type { Message } from 'discord.js';
import { callPlugins, executeModule } from './event-utils';
import { SernError } from '../core/structures/enums'
import { createSDT, fmt, resultPayload } from '../core/functions'
import { UnpackedDependencies } from '../types/utility';
import type { UnpackedDependencies } from '../types/utility';
import type { Module } from '../types/core-modules';
import { Context } from '../core/structures/context';

View File

@@ -23,7 +23,8 @@ const parseConfig = async (conf: Promise<Presence.Result>, setPresence: SetPrese
// If it's a promise, await it, otherwise use the value directly
return result instanceof Promise ? await result : result;
} catch (error) {
console.error(error);
// TODO process error
//console.error(error);
return state; // Return previous state on error
}
};
@@ -43,7 +44,6 @@ const parseConfig = async (conf: Promise<Presence.Result>, setPresence: SetPrese
processState(currentState)
.then(newState => {
//console.log(newState)
currentState = newState;
return setPresence(currentState)
})

View File

@@ -6,10 +6,11 @@ import type { UnpackedDependencies } from '../types/utility';
import type { Emitter } from '../core/interfaces';
import { inspect } from 'util'
import { resultPayload } from '../core/functions';
import { Wrapper } from '../'
export default async function(deps: UnpackedDependencies, eventDir: string) {
export default async function(deps: UnpackedDependencies, wrapper: Wrapper) {
const eventModules: EventModule[] = [];
for await (const path of Files.readRecursive(eventDir)) {
for await (const path of Files.readRecursive(wrapper.events!)) {
let { module } = await Files.importModule<Module>(path);
await callInitPlugins(module, deps)
eventModules.push(module as EventModule);
@@ -40,17 +41,20 @@ export default async function(deps: UnpackedDependencies, eventDir: string) {
const execute = async (...args: any[]) => {
try {
if(args) {
if('once' in module) { source.removeListener(module.name!, execute); }
if('once' in module) { source.removeListener(String(module.name!), execute); }
await Reflect.apply(module.execute, null, args);
}
} catch(e) {
const err = e instanceof Error ? e : Error(inspect(e, { colors: true }));
//@ts-ignore
if(!report.emit('error', resultPayload('failure', module, err))) {
logger?.error({ message: inspect(err) });
}
}
}
source.addListener(module.name!, execute)
source.addListener(String(module.name!), execute)
}
}

View File

@@ -38,7 +38,7 @@ export type {
} from './types/core-plugin';
export type { Payload, SernEventsMapping } from './types/utility';
export type { Payload, SernEventsMapping, Wrapper } from './types/utility';
export {
commandModule,

View File

@@ -11,16 +11,11 @@ import ready from './handlers/ready';
import { interactionHandler } from './handlers/interaction';
import { messageHandler } from './handlers/message'
import { presenceHandler } from './handlers/presence';
import { UnpackedDependencies } from './types/utility';
import { UnpackedDependencies, Wrapper } from './types/utility';
import type { Presence} from './core/presences';
import { registerTasks } from './handlers/tasks';
interface Wrapper {
commands: string;
defaultPrefix?: string;
events?: string;
tasks?: string;
}
/**
* @since 1.0.0
* @param maybeWrapper Options to pass into sern.
@@ -39,7 +34,7 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
const deps = useContainerRaw().deps<UnpackedDependencies>();
if (maybeWrapper.events !== undefined) {
eventsHandler(deps, maybeWrapper.events)
eventsHandler(deps, maybeWrapper)
.then(() => {
deps['@sern/logger']?.info({ message: "Events registered" });
});

View File

@@ -1,11 +1,9 @@
import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js';
import type { Module } from './core-modules';
import type { Result } from '../core/structures/result';
export type Awaitable<T> = PromiseLike<T> | T;
export type Dictionary = Record<string, unknown>
export type VoidResult = Result<void, void>;
export type AnyFunction = (...args: any[]) => unknown;
export interface SernEventsMapping {
@@ -26,3 +24,11 @@ export type UnpackedDependencies = {
[K in keyof Dependencies]: UnpackFunction<Dependencies[K]>
}
export type ReplyOptions = string | Omit<InteractionReplyOptions, 'fetchReply'> | MessageReplyOptions;
export interface Wrapper {
commands: string;
defaultPrefix?: string;
events?: string;
tasks?: string;
}