mirror of
https://github.com/sern-handler/handler
synced 2026-06-17 05:12:16 +00:00
revising cron modules and better error messages
This commit is contained in:
@@ -113,3 +113,4 @@ tsconfig-cjs.json
|
||||
tsconfig-esm.json
|
||||
|
||||
renovate.json
|
||||
fortnite
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@sern/handler",
|
||||
"packageManager": "yarn@3.5.0",
|
||||
"version": "3.3.4",
|
||||
"version": "4.0.0",
|
||||
"description": "A complete, customizable, typesafe, & reactive framework for discord bots.",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -35,10 +35,10 @@
|
||||
"author": "SernDevs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sern/ioc": "^1.0.4",
|
||||
"@sern/ioc": "^1.1.0",
|
||||
"callsites": "^3.1.0",
|
||||
"cron": "^3.1.7",
|
||||
"deepmerge": "^4.3.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"rxjs": "^7.8.0",
|
||||
"ts-results-es": "^4.1.0"
|
||||
},
|
||||
|
||||
@@ -67,8 +67,8 @@ export async function makeDependencies (conf: ValidDependencyConfig) {
|
||||
container.addSingleton('@sern/errors', new __Services.DefaultErrorHandling);
|
||||
container.addSingleton('@sern/modules', new Map);
|
||||
container.addSingleton('@sern/emitter', new EventEmitter)
|
||||
container.addWiredSingleton('@sern/cron',
|
||||
(deps) => new __Services.Cron(deps as unknown as Dependencies))
|
||||
container.addWiredSingleton('@sern/scheduler',
|
||||
(deps) => new __Services.CronScheduler(deps as unknown as Dependencies))
|
||||
conf(dependencyBuilder(container));
|
||||
await container.ready();
|
||||
}
|
||||
|
||||
@@ -39,10 +39,8 @@ export const Presence = {
|
||||
* @example
|
||||
* ```ts
|
||||
* Presence.of({
|
||||
* activities: [
|
||||
* { name: "Chilling out" }
|
||||
* ]
|
||||
* }).once() // Sets the presence once, with what's provided in '.of()'
|
||||
* activities: [{ name: "Chilling out" }]
|
||||
* }).once() // Sets the presence once, with what's provided in '.of()'
|
||||
* ```
|
||||
*/
|
||||
once: () => root
|
||||
@@ -50,7 +48,7 @@ export const Presence = {
|
||||
}
|
||||
}
|
||||
export declare namespace Presence {
|
||||
type Config<T extends (keyof Dependencies)[]> = {
|
||||
export type Config<T extends (keyof Dependencies)[]> = {
|
||||
inject?: [...T]
|
||||
execute: (...v: IntoDependencies<T>) => Presence.Result;
|
||||
|
||||
|
||||
43
src/core/schedule.ts
Normal file
43
src/core/schedule.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CronJob } from 'cron';
|
||||
export class TaskScheduler {
|
||||
private __tasks: Map<string, CronJob> = new Map();
|
||||
|
||||
scheduleTask(taskName: string, cronExpression: string, task: () => void): boolean {
|
||||
if (this.__tasks.has(taskName)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const job = new CronJob(cronExpression, task);
|
||||
job.start();
|
||||
this.__tasks.set(taskName, job);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private stopTask(taskName: string): boolean {
|
||||
const job = this.__tasks.get(taskName);
|
||||
if (job) {
|
||||
job.stop();
|
||||
this.__tasks.delete(taskName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private restartTask(taskName: string): boolean {
|
||||
const job = this.__tasks.get(taskName);
|
||||
if (job) {
|
||||
job.start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
tasks(): string[] {
|
||||
return Array.from(this.__tasks.keys());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { LogPayload, Logging, ErrorHandling, Emitter } from '../interfaces';
|
||||
import { AnyFunction, UnpackedDependencies } from '../../types/utility';
|
||||
import cron from 'node-cron'
|
||||
import type { CronEventCommand, Module } from '../../types/core-modules'
|
||||
import { EventType } from './enums';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 2.0.0
|
||||
@@ -42,48 +40,30 @@ export class DefaultLogging implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
export class Cron implements Emitter {
|
||||
export class CronScheduler {
|
||||
tasks: string[] = [];
|
||||
modules: Map<string, CronEventCommand> = new Map();
|
||||
constructor(private deps: UnpackedDependencies) {}
|
||||
private sanityCheck(eventName: string | symbol) : asserts eventName is string {
|
||||
if(typeof eventName === 'symbol') throw Error("Cron cannot add symbol based listener")
|
||||
}
|
||||
addCronModule(module: Module) {
|
||||
if(module.type !== EventType.Cron) {
|
||||
throw Error("Can only add cron modules");
|
||||
}
|
||||
//@ts-ignore
|
||||
if(!cron.validate(module.pattern)) {
|
||||
throw Error("Invalid cron expression while adding " + module.name)
|
||||
}
|
||||
(module as CronEventCommand)
|
||||
this.modules.set(module.name!, module as CronEventCommand);
|
||||
}
|
||||
addListener(eventName: string | symbol, listener: AnyFunction): this {
|
||||
this.sanityCheck(eventName);
|
||||
const retrievedModule = this.modules.get(eventName);
|
||||
if(!retrievedModule) throw Error("Adding task: module " +eventName +"was not found");
|
||||
const { pattern, name, runOnInit, timezone } = retrievedModule;
|
||||
cron.schedule(pattern,
|
||||
(date) => listener({ date, deps: this.deps }),
|
||||
{ name, runOnInit, timezone, scheduled: true });
|
||||
return this;
|
||||
}
|
||||
removeListener(eventName: string | symbol, listener: AnyFunction) {
|
||||
this.sanityCheck(eventName);
|
||||
const retrievedModule = this.modules.get(eventName);
|
||||
if(!retrievedModule) throw Error("Removing cron: module " +eventName +"was not found");
|
||||
const task = cron.getTasks().get(retrievedModule.name!)
|
||||
if(!task) throw Error("Finding cron task with"+ retrievedModule.name + " not found");
|
||||
task.stop();
|
||||
return this;
|
||||
}
|
||||
emit(eventName: string | symbol, ...payload: any[]): boolean {
|
||||
this.sanityCheck(eventName);
|
||||
const retrievedModule = this.modules.get(eventName);
|
||||
if(!retrievedModule) throw Error("Removing cron: module " +eventName +"was not found");
|
||||
const task= cron.getTasks().get(retrievedModule.name!)
|
||||
return task?.emit(eventName, payload) ?? false;
|
||||
}
|
||||
// addListener(eventName: string | symbol, listener: AnyFunction): this {
|
||||
// const retrievedModule = this.modules.get(eventName);
|
||||
// if(!retrievedModule) throw Error("Adding task: module " +eventName +"was not found");
|
||||
// const { pattern, name, runOnInit, timezone } = retrievedModule;
|
||||
// cron.schedule(pattern,
|
||||
// (date) => listener({ date, deps: this.deps }),
|
||||
// { name, runOnInit, timezone, scheduled: true });
|
||||
// return this;
|
||||
// }
|
||||
// removeListener(eventName: string | symbol, listener: AnyFunction) {
|
||||
// const retrievedModule = this.modules.get(eventName);
|
||||
// if(!retrievedModule) throw Error("Removing cron: module " +eventName +"was not found");
|
||||
// const task = cron.getTasks().get(retrievedModule.name!)
|
||||
// if(!task) throw Error("Finding cron task with"+ retrievedModule.name + " not found");
|
||||
// task.stop();
|
||||
// return this;
|
||||
// }
|
||||
// emit(eventName: string | symbol, ...payload: any[]): boolean {
|
||||
// const retrievedModule = this.modules.get(eventName);
|
||||
// if(!retrievedModule) throw Error("Removing cron: module " +eventName +"was not found");
|
||||
// const task= cron.getTasks().get(retrievedModule.name!)
|
||||
// return task?.emit(eventName, payload) ?? false;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ export enum EventType {
|
||||
* Could be for example, `process` events, database events
|
||||
*/
|
||||
External,
|
||||
Cron
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -262,10 +262,9 @@ export function intoTask(onStop: (m: Module) => unknown) {
|
||||
return createResultResolver({ onStop, onNext });
|
||||
}
|
||||
|
||||
export const handleCrash =
|
||||
({ "@sern/errors": err, '@sern/emitter': sem, '@sern/logger': log } : UnpackedDependencies) =>
|
||||
export const handleCrash = ({ "@sern/errors": err, '@sern/emitter': sem, '@sern/logger': log } : UnpackedDependencies, metadata: string) =>
|
||||
pipe(catchError(handleError(err, sem, log)),
|
||||
finalize(() => {
|
||||
log?.info({ message: 'A stream closed or reached end of lifetime' });
|
||||
log?.info({ message: 'A stream closed: ' + metadata });
|
||||
disposeAll(log);
|
||||
}))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Interaction } from 'discord.js';
|
||||
import { mergeMap, merge, concatMap, EMPTY } from 'rxjs';
|
||||
import { createInteractionHandler, executeModule, intoTask, sharedEventStream, filterTap } from './event-utils';
|
||||
import { createInteractionHandler, executeModule, intoTask, sharedEventStream, filterTap, handleCrash } from './event-utils';
|
||||
import { SernError } from '../core/structures/enums'
|
||||
import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload } from '../core/functions'
|
||||
import { UnpackedDependencies } from '../types/utility';
|
||||
@@ -25,5 +25,6 @@ export default function interactionHandler(deps: UnpackedDependencies, defaultPr
|
||||
if(payload)
|
||||
return executeModule(emitter, payload)
|
||||
return EMPTY;
|
||||
}));
|
||||
}),
|
||||
handleCrash(deps, "interaction handling"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EMPTY, mergeMap, concatMap } from 'rxjs';
|
||||
import type { Message } from 'discord.js';
|
||||
import { createMessageHandler, executeModule, intoTask, sharedEventStream, filterTap} from './event-utils';
|
||||
import { createMessageHandler, executeModule, intoTask, sharedEventStream, filterTap, handleCrash} from './event-utils';
|
||||
import { SernError } from '../core/structures/enums'
|
||||
import { resultPayload } from '../core/functions'
|
||||
import { UnpackedDependencies } from '../types/utility';
|
||||
@@ -44,5 +44,7 @@ function (deps: UnpackedDependencies, defaultPrefix?: string) {
|
||||
if(payload)
|
||||
return executeModule(emitter, payload)
|
||||
return EMPTY;
|
||||
}));
|
||||
}),
|
||||
handleCrash(deps, "message handling")
|
||||
)
|
||||
}
|
||||
|
||||
23
src/handlers/tasks.ts
Normal file
23
src/handlers/tasks.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TaskScheduler } from "../core/schedule"
|
||||
import * as Files from '../core/module-loading'
|
||||
import { UnpackedDependencies } from "../types/utility";
|
||||
|
||||
interface ScheduledTaskModule {
|
||||
name?: string;
|
||||
description?: string;
|
||||
pattern: string;
|
||||
execute(deps: UnpackedDependencies, tasks: string[]): any
|
||||
}
|
||||
|
||||
export const registerTasks = async (path: string, deps: UnpackedDependencies) => {
|
||||
const taskManager = new TaskScheduler()
|
||||
|
||||
for await (const f of Files.readRecursive(path)) {
|
||||
let { module } = await Files.importModule<ScheduledTaskModule>(f);
|
||||
//module.name is assigned by Files.importModule<>
|
||||
taskManager.scheduleTask(module.name!, module.pattern, () => {
|
||||
module.execute(deps, taskManager.tasks())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,6 @@ const intoDispatcher = (deps: UnpackedDependencies) =>
|
||||
return eventDispatcher(deps, module, deps['@sern/client']);
|
||||
case EventType.External:
|
||||
return eventDispatcher(deps, module, deps[module.emitter]);
|
||||
case EventType.Cron: {
|
||||
//@ts-ignore
|
||||
const cron = deps['@sern/cron'];
|
||||
cron.addCronModule(module);
|
||||
return eventDispatcher(deps, module, cron);
|
||||
}
|
||||
default: throw Error(SernError.InvalidModuleType + ' while creating event handler');
|
||||
}
|
||||
};
|
||||
@@ -34,6 +28,6 @@ export default async function(deps: UnpackedDependencies, eventDir: string) {
|
||||
from(eventModules)
|
||||
.pipe(map(intoDispatcher(deps)),
|
||||
mergeAll(), // all eventListeners are turned on
|
||||
handleCrash(deps))
|
||||
handleCrash(deps, "event modules"))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ import { presenceHandler } from './handlers/presence';
|
||||
import { handleCrash } from './handlers/event-utils';
|
||||
import { UnpackedDependencies } 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
|
||||
@@ -57,11 +59,14 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
||||
}
|
||||
presenceHandler(presencePath.path, setPresence).subscribe();
|
||||
}
|
||||
if(maybeWrapper.tasks) {
|
||||
registerTasks(maybeWrapper.tasks, deps);
|
||||
}
|
||||
})
|
||||
.catch(err => { throw err });
|
||||
|
||||
const messages$ = messageHandler(deps, maybeWrapper.defaultPrefix);
|
||||
const interactions$ = interactionHandler(deps, maybeWrapper.defaultPrefix);
|
||||
// listening to the message stream and interaction stream
|
||||
merge(messages$, interactions$).pipe(handleCrash(deps)).subscribe();
|
||||
merge(messages$, interactions$).subscribe();
|
||||
}
|
||||
|
||||
@@ -58,14 +58,7 @@ export interface ExternalEventCommand extends Module {
|
||||
type: EventType.External;
|
||||
execute(...args: unknown[]): Awaitable<unknown>;
|
||||
}
|
||||
export interface CronEventCommand extends Module {
|
||||
type: EventType.Cron;
|
||||
name?: string;
|
||||
pattern: string;
|
||||
runOnInit?: boolean
|
||||
timezone?: string;
|
||||
execute(...args: unknown[]): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
|
||||
export interface ContextMenuUser extends Module {
|
||||
type: CommandType.CtxUser;
|
||||
@@ -142,7 +135,7 @@ export interface BothCommand extends Module {
|
||||
execute: (ctx: Context, tbd: SDT) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand | CronEventCommand;
|
||||
export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand;
|
||||
export type CommandModule =
|
||||
| TextCommand
|
||||
| SlashCommand
|
||||
@@ -178,7 +171,6 @@ export interface EventModuleDefs {
|
||||
[EventType.Sern]: SernEventCommand;
|
||||
[EventType.Discord]: DiscordEventCommand;
|
||||
[EventType.External]: ExternalEventCommand;
|
||||
[EventType.Cron]: CronEventCommand;
|
||||
}
|
||||
|
||||
export interface SernAutocompleteData
|
||||
|
||||
54
yarn.lock
54
yarn.lock
@@ -546,16 +546,16 @@ __metadata:
|
||||
resolution: "@sern/handler@workspace:."
|
||||
dependencies:
|
||||
"@faker-js/faker": ^8.0.1
|
||||
"@sern/ioc": ^1.0.4
|
||||
"@sern/ioc": ^1.1.0
|
||||
"@types/node": ^20.0.0
|
||||
"@types/node-cron": ^3.0.11
|
||||
"@typescript-eslint/eslint-plugin": 5.58.0
|
||||
"@typescript-eslint/parser": 5.59.1
|
||||
callsites: ^3.1.0
|
||||
cron: ^3.1.7
|
||||
deepmerge: ^4.3.1
|
||||
discord.js: ^14.15.3
|
||||
eslint: 8.39.0
|
||||
node-cron: ^3.0.3
|
||||
rxjs: ^7.8.0
|
||||
ts-results-es: ^4.1.0
|
||||
typescript: 5.0.2
|
||||
@@ -563,10 +563,10 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@sern/ioc@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@sern/ioc@npm:1.0.4"
|
||||
checksum: 3d1a63099b3e8ff0d44bb73007b1d66c3f3b27cf7a193c2d9122e021cb72be1ced535ea98efaf72602f371a489177e3b144ed72da8d7de80887c0408ce79cce2
|
||||
"@sern/ioc@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@sern/ioc@npm:1.1.0"
|
||||
checksum: 0882ef51c3fcd28e7fe803762f2a8d7eb3dca4e494d3475ef7d4a43158d3b24243bda3679c3d58485c89bdc820719d22351007503e44b5cf8e6f2d0efe342921
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -591,6 +591,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:~3.4.0":
|
||||
version: 3.4.2
|
||||
resolution: "@types/luxon@npm:3.4.2"
|
||||
checksum: 6f92d5bd02e89f310395753506bcd9cef3a56f5940f7a50db2a2b9822bce753553ac767d143cb5b4f9ed5ddd4a84e64f89ff538082ceb4d18739af7781b56925
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-cron@npm:^3.0.11":
|
||||
version: 3.0.11
|
||||
resolution: "@types/node-cron@npm:3.0.11"
|
||||
@@ -1120,6 +1127,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cron@npm:^3.1.7":
|
||||
version: 3.1.7
|
||||
resolution: "cron@npm:3.1.7"
|
||||
dependencies:
|
||||
"@types/luxon": ~3.4.0
|
||||
luxon: ~3.4.0
|
||||
checksum: d98ee5297543c138221d96dd49270bf6576db80134e6041f4ce4a3c0cb6060863d76910209b34fee66fbf134461449ec3bd283d6a76d1c50da220cde7fc10c65
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
|
||||
version: 7.0.3
|
||||
resolution: "cross-spawn@npm:7.0.3"
|
||||
@@ -2065,6 +2082,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"luxon@npm:~3.4.0":
|
||||
version: 3.4.4
|
||||
resolution: "luxon@npm:3.4.4"
|
||||
checksum: 36c1f99c4796ee4bfddf7dc94fa87815add43ebc44c8934c924946260a58512f0fd2743a629302885df7f35ccbd2d13f178c15df046d0e3b6eb71db178f1c60c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-bytes.js@npm:^1.10.0":
|
||||
version: 1.10.0
|
||||
resolution: "magic-bytes.js@npm:1.10.0"
|
||||
@@ -2292,15 +2316,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-cron@npm:^3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "node-cron@npm:3.0.3"
|
||||
dependencies:
|
||||
uuid: 8.3.2
|
||||
checksum: 351c37491ebf717d0ae69cc941465de118e5c2ef5d48bc3f87c98556241b060f100402c8a618c7b86f9f626b44756b20d8b5385b70e52f80716f21e55db0f1c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 10.1.0
|
||||
resolution: "node-gyp@npm:10.1.0"
|
||||
@@ -3074,15 +3089,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uuid@npm:8.3.2":
|
||||
version: 8.3.2
|
||||
resolution: "uuid@npm:8.3.2"
|
||||
bin:
|
||||
uuid: dist/bin/uuid
|
||||
checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-node@npm:1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "vite-node@npm:1.6.0"
|
||||
|
||||
Reference in New Issue
Block a user