mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
documentation+clean
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
src/sern.ts
11
src/sern.ts
@@ -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" });
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user