Skip to content

Commit

Permalink
up to speed with event modules
Browse files Browse the repository at this point in the history
  • Loading branch information
jacoobes committed May 15, 2024
1 parent 16a84e8 commit 0d82658
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 50 deletions.
4 changes: 4 additions & 0 deletions src/core/ioc/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as __Services from '../structures/default-services';
import { UnpackedDependencies } from '../../types/utility';
import type { Logging } from '../interfaces';
import { __add_container, __init_container, __swap_container, useContainerRaw } from './global';
import { EventEmitter } from 'node:events';

export function disposeAll(logger: Logging|undefined) {
useContainerRaw()
Expand Down Expand Up @@ -72,6 +73,7 @@ async function composeRoot(
__add_container('@sern/errors', new __Services.DefaultErrorHandling());
__add_container('@sern/cron', {})
__add_container('@sern/modules', new Map())
__add_container('@sern/emitter', new EventEmitter())
//Build the container based on the callback provided by the user
conf.build(container as Container);

Expand All @@ -97,6 +99,8 @@ export async function makeDependencies (conf: ValidDependencyConfig) {
}
__add_container('@sern/errors', new __Services.DefaultErrorHandling());
__add_container('@sern/cron', {})
__add_container('@sern/modules', new Map())
__add_container('@sern/emitter', new EventEmitter())
await useContainerRaw().ready();
} else {
await composeRoot(useContainerRaw(), conf);
Expand Down
2 changes: 1 addition & 1 deletion src/core/module-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function* readRecursive(dir: string): AsyncGenerator<string> {
const files = await readdir(dir, { withFileTypes: true });

for (const file of files) {
const fullPath = path.join(dir, file.name);
const fullPath = path.posix.resolve(dir, file.name);
if (file.isDirectory()) {
if (!file.name.startsWith('!')) {
yield* readRecursive(fullPath);
Expand Down
10 changes: 5 additions & 5 deletions src/core/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ export function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, lo
//// Temporary until i get rxjs operators working on ts-results-es
export const filterTap = <K, R>(onErr: (e: R) => void): OperatorFunction<Result<K, R>, K> =>
concatMap(result => {
if(result.isOk()) {
return of(result.value)
}
onErr(result.error);
return EMPTY
if(result.isOk()) {
return of(result.value)
}
onErr(result.error);
return EMPTY;
})
12 changes: 5 additions & 7 deletions src/handlers/event-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[
return [ctx, args] as [Context, Args];
}

function intoPayload(module: Processed<Module>, ) {
function intoPayload(module: Module) {
return pipe(map(arrayifySource),
map(args => ({ module, args })));
}
Expand All @@ -58,12 +58,13 @@ const createResult = createResultResolver<
* @param module
* @param source
*/
export function eventDispatcher(module: Processed<Module>, source: unknown) {
export function eventDispatcher(module: Module, source: unknown) {
assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`);

const execute: OperatorFunction<unknown[], unknown> =
concatMap(async args => module.execute(...args));
return fromEvent(source, module.name)
return fromEvent(source, module.name!)
//@ts-ignore
.pipe(intoPayload(module),
concatMap(createResult),
execute);
Expand Down Expand Up @@ -190,12 +191,10 @@ export function executeModule(
return EMPTY;
}
return throwError(() => resultPayload(PayloadType.Failure, module, result.error));

}),
);
};


/**
* A higher order function that
* - creates a stream of {@link VoidResult} { config.createStream }
Expand All @@ -221,8 +220,7 @@ export function createResultResolver<
result.isErr() && config.onStop?.(args.module);
}),
everyPluginOk,
filterMapTo(() => config.onNext(args)),
);
filterMapTo(() => config.onNext(args)));
};
};

Expand Down
5 changes: 3 additions & 2 deletions src/handlers/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import { createInteractionHandler, executeModule, makeModuleExecutor } from './e
import { SernError } from '../core/structures/enums'
import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload, } from '../core/functions'
import { UnpackedDependencies } from '../types/utility';
import { Emitter } from '../core/interfaces';

export function interactionHandler(deps: UnpackedDependencies) {
export default function interactionHandler(deps: UnpackedDependencies) {
//i wish javascript had clojure destructuring
const { '@sern/modules': modules,
'@sern/client': client,
'@sern/logger': log,
'@sern/errors': err,
'@sern/emitter': emitter } = deps
const interactionStream$ = sharedEventStream<Interaction>(client, 'interactionCreate');
const interactionStream$ = sharedEventStream<Interaction>(client as unknown as Emitter, 'interactionCreate');
const handle = createInteractionHandler(interactionStream$, modules);

const interactionHandler$ = merge(handle(isMessageComponent),
Expand Down
5 changes: 3 additions & 2 deletions src/handlers/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PayloadType, SernError } from '../core/structures/enums'
import { resultPayload } from '../core/functions'
import { filterTap, sharedEventStream } from '../core/operators'
import { UnpackedDependencies } from '../types/utility';
import { Emitter } from '..';

/**
* Ignores messages from any person / bot except itself
Expand All @@ -19,15 +20,15 @@ function hasPrefix(prefix: string, content: string) {
return (prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0);
}

export function messageHandler({"@sern/emitter": emitter, '@sern/errors':err,
export default function message({"@sern/emitter": emitter, '@sern/errors':err,
'@sern/logger': log, '@sern/client': client,
'@sern/modules': commands}: UnpackedDependencies,
defaultPrefix: string | undefined) {
if (!defaultPrefix) {
log?.debug({ message: 'No prefix found. message handler shutting down' });
return EMPTY;
}
const messageStream$ = sharedEventStream<Message>(client, 'messageCreate');
const messageStream$ = sharedEventStream<Message>(client as unknown as Emitter, 'messageCreate');
const handle = createMessageHandler(messageStream$, defaultPrefix, commands);

const msgCommands$ = handle(isNonBot(defaultPrefix));
Expand Down
9 changes: 6 additions & 3 deletions src/handlers/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Files from '../core/module-loading'
import { once } from 'events';
import { resultPayload } from '../core/functions';
import { PayloadType } from '..';
import { SernError } from '../core/structures/enums';
import { CommandType, SernError } from '../core/structures/enums';
import { Module } from '../types/core-modules';
import { UnpackedDependencies } from '../types/utility';

Expand All @@ -14,15 +14,18 @@ export default async function(dir: string, deps : UnpackedDependencies) {
log?.info({ message: "Waiting on discord client to be ready..." })
await once(client, "ready");
log?.info({ message: "Client signaled ready, registering modules" });

// https://observablehq.com/@ehouais/multiple-promises-as-an-async-generator
// possibly optimize to concurrently import modules
for await (const path of Files.readRecursive(dir)) {
const { module } = await Files.importModule<Module>(path);
const validType = module.type >= 0 && module.type <= 1 << 10;
const validType = module.type >= CommandType.Text && module.type <= CommandType.ChannelSelect;
if(!validType) {
throw Error(`Found ${module.name} at ${module.meta.absPath}, which has an incorrect \`type\``);
}
for(const plugin of module.plugins) {
const res = await plugin.execute({ module, absPath: module.meta.absPath });
if(res.isErr()) {
if(res.isErr()) {
sEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
throw Error("Plugin failed with controller.stop()");
}
Expand Down
58 changes: 35 additions & 23 deletions src/handlers/user-defined-events.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
import { EventType, SernError } from '../core/structures/enums';
import { eventDispatcher } from './event-utils'
import type { EventModule, Processed } from '../types/core-modules';
import { EventType, PayloadType, SernError } from '../core/structures/enums';
import { eventDispatcher, handleCrash } from './event-utils'
import { EventModule, Module, Processed } from '../types/core-modules';
import * as Files from '../core/module-loading'
import type { UnpackedDependencies } from '../types/utility';
import { resultPayload } from '../core/functions';
import { from, map, mergeAll } from 'rxjs';

export default function(deps: UnpackedDependencies, eventDir: string) {
//code smell
const intoDispatcher = (e: { module: Processed<EventModule> }) => {
switch (e.module.type) {
const intoDispatcher = (deps: UnpackedDependencies) =>
(module : EventModule) => {
switch (module.type) {
case EventType.Sern:
return eventDispatcher(e.module, deps['@sern/emitter']);
return eventDispatcher(module, deps['@sern/emitter']);
case EventType.Discord:
return eventDispatcher(e.module, deps['@sern/client']);
return eventDispatcher(module, deps['@sern/client']);
case EventType.External:
return eventDispatcher(e.module, deps[e.module.emitter]);
return eventDispatcher(module, deps[module.emitter]);
case EventType.Cron:
//@ts-ignore TODO
return eventDispatcher(e.module, deps['@sern/cron'])
return eventDispatcher(module, deps['@sern/cron'])
default:
throw Error(SernError.InvalidModuleType + ' while creating event handler');
}
};
Files.readRecursive(eventDir)
//buildModules<EventModule>(allPaths)
// pipe(
// callInitPlugins(emitter),
// map(intoDispatcher),
// /**
// * Where all events are turned on
// */
// mergeAll(),
// handleCrash(err, emitter, log))
// .subscribe();
};

export default async function(deps: UnpackedDependencies, eventDir: string) {
const eventModules: EventModule[] = [];
for await (const path of Files.readRecursive(eventDir)) {
const { module } = await Files.importModule<Module>(path);
for(const plugin of module.plugins) {
const res = await plugin.execute({ module, absPath: module.meta.absPath });
if(res.isErr()) {
deps['@sern/emitter'].emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
throw Error("Plugin failed with controller.stop()");
}
}
eventModules.push(module as EventModule);
}
from(eventModules)
.pipe(map(intoDispatcher(deps)),
/**
* Where all events are turned on
*/
mergeAll(),
handleCrash(deps))
.subscribe();
}
16 changes: 9 additions & 7 deletions src/sern.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import callsites from 'callsites';
import * as Files from './core/module-loading';
import { merge } from 'rxjs';
import { Services } from './core/ioc';
import eventsHandler from './handlers/user-defined-events';
import ready from './handlers/ready';
import { messageHandler } from './handlers/message';
import { interactionHandler } from './handlers/interaction';
import messageHandler from './handlers/message';
import interactionHandler from './handlers/interaction';
import { presenceHandler } from './handlers/presence';
import { Client } from 'discord.js';
import { handleCrash } from './handlers/event-utils';
import { useContainerRaw } from './core/ioc/global';
import { UnpackedDependencies } from './types/utility';
Expand Down Expand Up @@ -35,7 +33,12 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
const deps = useContainerRaw().deps<UnpackedDependencies>();

if (maybeWrapper.events !== undefined) {
eventsHandler(deps, maybeWrapper.events);
eventsHandler(deps, maybeWrapper.events)
.then(() => {
deps['@sern/logger']?.info({ message: "Events registered" });
});
} else {
deps['@sern/logger']?.info({ message: "No events registered" });
}

const initCallsite = callsites()[1].getFileName();
Expand All @@ -47,8 +50,7 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
deps['@sern/logger']?.info({ message: `sern: registered in ${time} s` });
if(presencePath.exists) {
const setPresence = async (p: any) => {
//@ts-ignore
return (dependencies[3] as Client).user?.setPresence(p);
return deps['@sern/client'].user?.setPresence(p);
}
presenceHandler(presencePath.path, setPresence).subscribe();
}
Expand Down

0 comments on commit 0d82658

Please sign in to comment.