mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
feat: 4.2.0 load multiple directories & handleModuleErrors (#378)
* error-handling-draft * feat: array based module loading (#379) Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> * Update utility.ts * Update sern.ts * describesemanticsbetter --------- Co-authored-by: Duro <davidwright13503@gmail.com>
This commit is contained in:
@@ -3,10 +3,10 @@ import { once } from 'node:events';
|
|||||||
import { resultPayload } from '../core/functions';
|
import { resultPayload } from '../core/functions';
|
||||||
import { CommandType } from '../core/structures/enums';
|
import { CommandType } from '../core/structures/enums';
|
||||||
import { Module } from '../types/core-modules';
|
import { Module } from '../types/core-modules';
|
||||||
import type { UnpackedDependencies } from '../types/utility';
|
import type { UnpackedDependencies, Wrapper } from '../types/utility';
|
||||||
import { callInitPlugins } from './event-utils';
|
import { callInitPlugins } from './event-utils';
|
||||||
|
|
||||||
export default async function(dir: string, deps : UnpackedDependencies) {
|
export default async function(dirs: string | string[], deps : UnpackedDependencies) {
|
||||||
const { '@sern/client': client,
|
const { '@sern/client': client,
|
||||||
'@sern/logger': log,
|
'@sern/logger': log,
|
||||||
'@sern/emitter': sEmitter,
|
'@sern/emitter': sEmitter,
|
||||||
@@ -17,16 +17,21 @@ export default async function(dir: string, deps : UnpackedDependencies) {
|
|||||||
|
|
||||||
// https://observablehq.com/@ehouais/multiple-promises-as-an-async-generator
|
// https://observablehq.com/@ehouais/multiple-promises-as-an-async-generator
|
||||||
// possibly optimize to concurrently import modules
|
// possibly optimize to concurrently import modules
|
||||||
for await (const path of Files.readRecursive(dir)) {
|
|
||||||
let { module } = await Files.importModule<Module>(path);
|
const directories = Array.isArray(dirs) ? dirs : [dirs];
|
||||||
const validType = module.type >= CommandType.Text && module.type <= CommandType.ChannelSelect;
|
|
||||||
if(!validType) {
|
for (const dir of directories) {
|
||||||
throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``);
|
for await (const path of Files.readRecursive(dir)) {
|
||||||
|
let { module } = await Files.importModule<Module>(path);
|
||||||
|
const validType = module.type >= CommandType.Text && module.type <= CommandType.ChannelSelect;
|
||||||
|
if(!validType) {
|
||||||
|
throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``);
|
||||||
|
}
|
||||||
|
const resultModule = await callInitPlugins(module, deps, true);
|
||||||
|
// FREEZE! no more writing!!
|
||||||
|
commands.set(resultModule.meta.id, Object.freeze(resultModule));
|
||||||
|
sEmitter.emit('module.register', resultPayload('success', resultModule));
|
||||||
}
|
}
|
||||||
const resultModule = await callInitPlugins(module, deps, true);
|
|
||||||
// FREEZE! no more writing!!
|
|
||||||
commands.set(resultModule.meta.id, Object.freeze(resultModule));
|
|
||||||
sEmitter.emit('module.register', resultPayload('success', resultModule));
|
|
||||||
}
|
}
|
||||||
sEmitter.emit('modulesLoaded');
|
sEmitter.emit('modulesLoaded');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import * as Files from '../core/module-loading'
|
import * as Files from '../core/module-loading'
|
||||||
import { UnpackedDependencies } from "../types/utility";
|
import { UnpackedDependencies, Wrapper } from "../types/utility";
|
||||||
import type { ScheduledTask } from "../types/core-modules";
|
import type { ScheduledTask } from "../types/core-modules";
|
||||||
import { relative } from "path";
|
import { relative } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
export const registerTasks = async (tasksPath: string, deps: UnpackedDependencies) => {
|
export const registerTasks = async (tasksDirs: string | string[], deps: UnpackedDependencies) => {
|
||||||
const taskManager = deps['@sern/scheduler']
|
const taskManager = deps['@sern/scheduler']
|
||||||
for await (const f of Files.readRecursive(tasksPath)) {
|
|
||||||
let { module } = await Files.importModule<ScheduledTask>(f);
|
const directories = Array.isArray(tasksDirs) ? tasksDirs : [tasksDirs];
|
||||||
//module.name is assigned by Files.importModule<>
|
|
||||||
// the id created for the task is unique
|
for (const dir of directories) {
|
||||||
const uuid = module.name+"/"+relative(tasksPath,fileURLToPath(f))
|
for await (const path of Files.readRecursive(dir)) {
|
||||||
taskManager.schedule(uuid, module, deps)
|
let { module } = await Files.importModule<ScheduledTask>(path);
|
||||||
|
//module.name is assigned by Files.importModule<>
|
||||||
|
// the id created for the task is unique
|
||||||
|
const uuid = module.name+"/"+relative(dir,fileURLToPath(path))
|
||||||
|
taskManager.schedule(uuid, module, deps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EventType, SernError } from '../core/structures/enums';
|
import { EventType, SernError } from '../core/structures/enums';
|
||||||
import { callInitPlugins } from './event-utils'
|
import { callInitPlugins } from './event-utils'
|
||||||
import { EventModule, Module } from '../types/core-modules';
|
import { EventModule } from '../types/core-modules';
|
||||||
import * as Files from '../core/module-loading'
|
import * as Files from '../core/module-loading'
|
||||||
import type { UnpackedDependencies } from '../types/utility';
|
import type { UnpackedDependencies } from '../types/utility';
|
||||||
import type { Emitter } from '../core/interfaces';
|
import type { Emitter } from '../core/interfaces';
|
||||||
@@ -10,11 +10,16 @@ import type { Wrapper } from '../'
|
|||||||
|
|
||||||
export default async function(deps: UnpackedDependencies, wrapper: Wrapper) {
|
export default async function(deps: UnpackedDependencies, wrapper: Wrapper) {
|
||||||
const eventModules: EventModule[] = [];
|
const eventModules: EventModule[] = [];
|
||||||
for await (const path of Files.readRecursive(wrapper.events!)) {
|
const eventDirs = Array.isArray(wrapper.events!) ? wrapper.events! : [wrapper.events!];
|
||||||
let { module } = await Files.importModule<Module>(path);
|
|
||||||
await callInitPlugins(module, deps)
|
for (const dir of eventDirs) {
|
||||||
eventModules.push(module as EventModule);
|
for await (const path of Files.readRecursive(dir)) {
|
||||||
|
let { module } = await Files.importModule<EventModule>(path);
|
||||||
|
await callInitPlugins(module, deps)
|
||||||
|
eventModules.push(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = deps['@sern/logger'], report = deps['@sern/emitter'];
|
const logger = deps['@sern/logger'], report = deps['@sern/emitter'];
|
||||||
for (const module of eventModules) {
|
for (const module of eventModules) {
|
||||||
let source: Emitter;
|
let source: Emitter;
|
||||||
|
|||||||
17
src/index.ts
17
src/index.ts
@@ -53,20 +53,3 @@ export * from './core/plugin';
|
|||||||
export { CommandType, PluginType, PayloadType, EventType } from './core/structures/enums';
|
export { CommandType, PluginType, PayloadType, EventType } from './core/structures/enums';
|
||||||
export { Context } from './core/structures/context';
|
export { Context } from './core/structures/context';
|
||||||
export { type CoreDependencies, makeDependencies, single, transient, Service, Services } from './core/ioc';
|
export { type CoreDependencies, makeDependencies, single, transient, Service, Services } from './core/ioc';
|
||||||
|
|
||||||
|
|
||||||
import type { Container } from '@sern/ioc';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This old signature will be incompatible with future versions of sern >= 4.0.0. See {@link makeDependencies}
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* To switch your old code:
|
|
||||||
await makeDependencies(({ add }) => {
|
|
||||||
add('@sern/client', new Client())
|
|
||||||
})
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export interface DependencyConfiguration {
|
|
||||||
build: (root: Container) => Container;
|
|
||||||
}
|
|
||||||
|
|||||||
23
src/sern.ts
23
src/sern.ts
@@ -11,7 +11,7 @@ import ready from './handlers/ready';
|
|||||||
import { interactionHandler } from './handlers/interaction';
|
import { interactionHandler } from './handlers/interaction';
|
||||||
import { messageHandler } from './handlers/message'
|
import { messageHandler } from './handlers/message'
|
||||||
import { presenceHandler } from './handlers/presence';
|
import { presenceHandler } from './handlers/presence';
|
||||||
import { UnpackedDependencies, Wrapper } from './types/utility';
|
import type { Payload, UnpackedDependencies, Wrapper } from './types/utility';
|
||||||
import type { Presence} from './core/presences';
|
import type { Presence} from './core/presences';
|
||||||
import { registerTasks } from './handlers/tasks';
|
import { registerTasks } from './handlers/tasks';
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ import { registerTasks } from './handlers/tasks';
|
|||||||
export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
const deps = useContainerRaw().deps<UnpackedDependencies>();
|
const deps = useContainerRaw().deps<UnpackedDependencies>();
|
||||||
|
|
||||||
if (maybeWrapper.events !== undefined) {
|
if (maybeWrapper.events !== undefined) {
|
||||||
eventsHandler(deps, maybeWrapper)
|
eventsHandler(deps, maybeWrapper)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -42,6 +41,22 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
|||||||
deps['@sern/logger']?.info({ message: "No events registered" });
|
deps['@sern/logger']?.info({ message: "No events registered" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// autohandle errors that occur in modules.
|
||||||
|
// convenient for rapid iteration
|
||||||
|
if(maybeWrapper.handleModuleErrors) {
|
||||||
|
if(!deps['@sern/logger']) {
|
||||||
|
throw Error('A logger is required to handleModuleErrors.\n A default logger is already supplied!');
|
||||||
|
}
|
||||||
|
deps['@sern/logger']?.info({ 'message': 'handleModuleErrors enabled' })
|
||||||
|
deps['@sern/emitter'].addListener('error', (payload: Payload) => {
|
||||||
|
if(payload.type === 'failure') {
|
||||||
|
deps['@sern/logger']?.error({ message: payload.reason })
|
||||||
|
} else {
|
||||||
|
deps['@sern/logger']?.warning({ message: "error event should only have payloads of 'failure'" });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const initCallsite = callsites()[1].getFileName();
|
const initCallsite = callsites()[1].getFileName();
|
||||||
const presencePath = Files.shouldHandle(initCallsite!, "presence");
|
const presencePath = Files.shouldHandle(initCallsite!, "presence");
|
||||||
//Ready event: load all modules and when finished, time should be taken and logged
|
//Ready event: load all modules and when finished, time should be taken and logged
|
||||||
@@ -60,10 +75,6 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => { throw err });
|
.catch(err => { throw err });
|
||||||
|
|
||||||
//const messages$ = messageHandler(deps, maybeWrapper.defaultPrefix);
|
|
||||||
interactionHandler(deps, maybeWrapper.defaultPrefix);
|
interactionHandler(deps, maybeWrapper.defaultPrefix);
|
||||||
messageHandler(deps, maybeWrapper.defaultPrefix)
|
messageHandler(deps, maybeWrapper.defaultPrefix)
|
||||||
// listening to the message stream and interaction stream
|
|
||||||
//merge(messages$, interactions$).subscribe();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,65 @@ export type UnpackedDependencies = {
|
|||||||
export type ReplyOptions = string | Omit<InteractionReplyOptions, 'fetchReply'> | MessageReplyOptions;
|
export type ReplyOptions = string | Omit<InteractionReplyOptions, 'fetchReply'> | MessageReplyOptions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface Wrapper
|
||||||
|
* @description Configuration interface for the sern framework. This interface defines
|
||||||
|
* the structure for configuring essential framework features including command handling,
|
||||||
|
* event management, and task scheduling.
|
||||||
|
*/
|
||||||
export interface Wrapper {
|
export interface Wrapper {
|
||||||
commands: string;
|
/**
|
||||||
|
* @property {string|string[]} commands
|
||||||
|
* @description Specifies the directory path where command modules are located.
|
||||||
|
* This is a required property that tells sern where to find and load command files.
|
||||||
|
* The path should be relative to the project root. If given an array, each directory is loaded in order
|
||||||
|
* they were declared. Order of modules in each directory is not guaranteed
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* commands: ["./dist/commands"]
|
||||||
|
*/
|
||||||
|
commands: string | string[];
|
||||||
|
/**
|
||||||
|
* @property {boolean} [handleModuleErrors]
|
||||||
|
* @description Optional flag to enable automatic error handling for modules.
|
||||||
|
* When enabled, sern will automatically catch and handle errors that occur
|
||||||
|
* during module execution, preventing crashes and providing error logging.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
handleModuleErrors?: boolean;
|
||||||
|
/**
|
||||||
|
* @property {string} [defaultPrefix]
|
||||||
|
* @description Optional prefix for text commands. This prefix will be used
|
||||||
|
* to identify text commands in messages. If not specified, text commands {@link CommandType.Text}
|
||||||
|
* will be disabled.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* defaultPrefix: "?"
|
||||||
|
*/
|
||||||
defaultPrefix?: string;
|
defaultPrefix?: string;
|
||||||
events?: string;
|
/**
|
||||||
tasks?: string;
|
* @property {string|string[]} [events]
|
||||||
|
* @description Optional directory path where event modules are located.
|
||||||
|
* If provided, Sern will automatically register and handle events from
|
||||||
|
* modules in this directory. The path should be relative to the project root.
|
||||||
|
* If given an array, each directory is loaded in order they were declared.
|
||||||
|
* Order of modules in each directory is not guaranteed.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* events: ["./dist/events"]
|
||||||
|
*/
|
||||||
|
events?: string | string[];
|
||||||
|
/**
|
||||||
|
* @property {string|string[]} [tasks]
|
||||||
|
* @description Optional directory path where scheduled task modules are located.
|
||||||
|
* If provided, Sern will automatically register and handle scheduled tasks
|
||||||
|
* from modules in this directory. The path should be relative to the project root.
|
||||||
|
* If given an array, each directory is loaded in order they were declared.
|
||||||
|
* Order of modules in each directory is not guaranteed.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* tasks: ["./dist/tasks"]
|
||||||
|
*/
|
||||||
|
tasks?: string | string[];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user