Skip to content

Commit

Permalink
revising cron modules and better error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jacoobes committed Jul 5, 2024
1 parent 92ca9eb commit c252854
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 105 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ tsconfig-cjs.json
tsconfig-esm.json

renovate.json
fortnite
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sern/handler",
"packageManager": "[email protected]",
"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",
Expand Down Expand Up @@ -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"
},
Expand Down
4 changes: 2 additions & 2 deletions src/core/ioc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
8 changes: 3 additions & 5 deletions src/core/presences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,16 @@ 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
};
}
}
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;

Expand Down
43 changes: 43 additions & 0 deletions src/core/schedule.ts
Original file line number Diff line number Diff line change
@@ -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());
}

}
70 changes: 25 additions & 45 deletions src/core/structures/default-services.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
// }
}
1 change: 0 additions & 1 deletion src/core/structures/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export enum EventType {
* Could be for example, `process` events, database events
*/
External,
Cron
}

/**
Expand Down
5 changes: 2 additions & 3 deletions src/handlers/event-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}))
5 changes: 3 additions & 2 deletions src/handlers/interaction.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -25,5 +25,6 @@ export default function interactionHandler(deps: UnpackedDependencies, defaultPr
if(payload)
return executeModule(emitter, payload)
return EMPTY;
}));
}),
handleCrash(deps, "interaction handling"));
}
6 changes: 4 additions & 2 deletions src/handlers/message.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -44,5 +44,7 @@ function (deps: UnpackedDependencies, defaultPrefix?: string) {
if(payload)
return executeModule(emitter, payload)
return EMPTY;
}));
}),
handleCrash(deps, "message handling")
)
}
23 changes: 23 additions & 0 deletions src/handlers/tasks.ts
Original file line number Diff line number Diff line change
@@ -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())
})
}

}
8 changes: 1 addition & 7 deletions src/handlers/user-defined-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
};
Expand All @@ -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();
}
7 changes: 6 additions & 1 deletion src/sern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
12 changes: 2 additions & 10 deletions src/types/core-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -178,7 +171,6 @@ export interface EventModuleDefs {
[EventType.Sern]: SernEventCommand;
[EventType.Discord]: DiscordEventCommand;
[EventType.External]: ExternalEventCommand;
[EventType.Cron]: CronEventCommand;
}

export interface SernAutocompleteData
Expand Down
Loading

0 comments on commit c252854

Please sign in to comment.