From faa0b4882e8f24b9a4e33f8b3d09dfd0ed0a19b1 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 4 May 2023 18:45:54 -0500 Subject: [PATCH] feat!: new module resolution algorithm --- src/core/module-loading.ts | 65 +++++---- src/core/structures/moduleStore.ts | 23 +-- src/handler/events/dispatchers/dispatchers.ts | 4 +- src/handler/events/generic.ts | 95 ++++++++++++ src/handler/events/interactions.ts | 136 +++--------------- src/handler/events/messages.ts | 37 +++-- src/handler/events/ready.ts | 87 ++--------- src/handler/events/userDefined.ts | 67 ++++----- src/handler/sern.ts | 2 +- 9 files changed, 213 insertions(+), 303 deletions(-) create mode 100644 src/handler/events/generic.ts diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index 973e611..89b9a30 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -1,35 +1,45 @@ import { readdirSync, statSync } from 'fs'; -import { join, basename } from 'path'; +import { readdir, stat } from 'fs/promises'; +import { join, basename, resolve } from 'path'; import { type Observable, from, mergeMap } from 'rxjs'; import { SernError } from './structures/errors'; import { type Result, Err, Ok } from 'ts-results-es'; -import { ImportPayload } from '../types/handler'; -import { pathToFileURL } from 'node:url'; +import { Processed } from '../types/handler'; +import { Module } from '../types/module'; +import * as assert from 'node:assert' +import * as util from 'node:util' -// Courtesy @Townsy45 -function readPath(dir: string, arrayOfFiles: string[] = []): string[] { - try { - const files = readdirSync(dir); - for (const file of files) { - if (statSync(dir + '/' + file).isDirectory()) readPath(dir + '/' + file, arrayOfFiles); - else arrayOfFiles.push(join(dir, '/', file)); - } - } catch (err) { - throw err; +async function* readPath(dir: string): AsyncGenerator { + try { + const files = await readdir(dir); + for (const file of files) { + const fullPath = join(dir, file); + const fileStats = await stat(fullPath); + if (fileStats.isDirectory()) { + yield* readPath(fullPath); + } else { + /// #if MODE === 'esm' + yield 'file:///'+fullPath; + /// #elif MODE === 'cjs' + yield fullPath; + /// #endif + } } - - return arrayOfFiles; + } catch (err) { + throw err; + } } -export const fmtFileName = (n: string) => n.substring(0, n.length - 3); -// export const isLazy = (n: string) => n.indexOf(".lazy.", n.length-9) !== -1; -export async function defaultModuleLoader( + +export const fmtFileName = (n: string) => n.substring(0, n.length - 3); + +export async function defaultModuleLoader( absPath: string, -): Promise, SernError>> { +): Promise, SernError>> { // prettier-ignore let module: T | undefined /// #if MODE === 'esm' - = (await import(pathToFileURL(absPath).toString())).default + = (await import(absPath)).default /// #elif MODE === 'cjs' = require(absPath).default; // eslint-disable-line /// #endif @@ -39,7 +49,12 @@ export async function defaultModuleLoader( try { module = new (module as unknown as new () => T)(); } catch {} - return Ok({ module, absPath }); + checkIsProcessed(module) + return Ok(module); +} + +function checkIsProcessed(m: T): asserts m is Processed { + assert.ok(m.name !== undefined, `name is not defined for ${util.format(m)}`) } /** @@ -48,15 +63,15 @@ export async function defaultModuleLoader( * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files * @param commandDir */ -export function buildModuleStream( +export function buildModuleStream( commandDir: string, -): Observable, SernError>> { +): Observable, SernError>> { const commands = getCommands(commandDir); return from(commands).pipe(mergeMap(defaultModuleLoader)); } -export function getCommands(dir: string): string[] { - return readPath(join(process.cwd(), dir)); +export function getCommands(dir: string) { + return readPath(resolve(dir)); } export function filename(path: string) { diff --git a/src/core/structures/moduleStore.ts b/src/core/structures/moduleStore.ts index 2ee861c..2bd32d4 100644 --- a/src/core/structures/moduleStore.ts +++ b/src/core/structures/moduleStore.ts @@ -1,7 +1,3 @@ -import type { CommandModule } from '../../types/module'; -import type { Processed } from '../../types/handler'; -import { ApplicationCommandType, ComponentType } from './enums'; - /** * @since 2.0.0 @@ -9,20 +5,7 @@ import { ApplicationCommandType, ComponentType } from './enums'; * This dependency is usually injected into ModuleManager */ export class ModuleStore { - readonly BothCommands = new Map>(); - readonly ApplicationCommands = { - [ApplicationCommandType.User]: new Map>(), - [ApplicationCommandType.Message]: new Map>(), - [ApplicationCommandType.ChatInput]: new Map>(), - }; - readonly ModalSubmit = new Map>(); - readonly TextCommands = new Map>(); - readonly InteractionHandlers = { - [ComponentType.Button]: new Map>(), - [ComponentType.StringSelect]: new Map>(), - [ComponentType.ChannelSelect]: new Map>(), - [ComponentType.MentionableSelect]: new Map>(), - [ComponentType.RoleSelect]: new Map>(), - [ComponentType.UserSelect]: new Map>(), - }; + readonly Commands = new Map(); } + + diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts index d443225..a14d8a7 100644 --- a/src/handler/events/dispatchers/dispatchers.ts +++ b/src/handler/events/dispatchers/dispatchers.ts @@ -2,7 +2,7 @@ import type { Processed } from '../../../types/handler'; import type { AutocompleteInteraction } from 'discord.js'; import { SernError } from '../../../core/structures'; import { treeSearch } from '../../../core/functions'; -import type { CommandModule, Module } from '../../../types/module'; +import type { BothCommand, CommandModule, Module } from '../../../types/module'; import { EventEmitter } from 'events'; import * as assert from 'assert'; import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; @@ -51,7 +51,7 @@ export function eventDispatcher(module: Processed, source: unknown) { } export function dispatchAutocomplete( - module: Processed, + module: Processed, interaction: AutocompleteInteraction, ) { const option = treeSearch(interaction, module.options); diff --git a/src/handler/events/generic.ts b/src/handler/events/generic.ts new file mode 100644 index 0000000..fc9fa74 --- /dev/null +++ b/src/handler/events/generic.ts @@ -0,0 +1,95 @@ +import { BaseInteraction, ChatInputCommandInteraction, Interaction, InteractionType } from "discord.js"; +import { Observable, filter, map, pipe, switchMap} from "rxjs"; +import { CommandType, ModuleManager, SernEmitter, SernError } from "../../core"; +import { filterMap, errTap } from '../../core/operators'; +import { defaultModuleLoader } from "../../core/module-loading"; +import { Processed } from "../../types/handler"; +import { AnyModule, BothCommand, CommandModule } from "../../types/module"; +import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from "./dispatchers"; +import { isAutocomplete } from "../../core/predicates"; +import { err } from "../../core/functions"; +import * as Files from '../../core/module-loading'; +import { sernMeta } from "../../commands"; + +/** + * Creates an RxJS observable that filters and maps incoming interactions to their respective modules. + * @param i An RxJS observable of interactions. + * @param mg The module manager instance used to retrieve the module path for each interaction. + * @returns A handler to create a RxJS observable of dispatchers that take incoming interactions and execute their corresponding modules. + */ +export function createHandler( + i: Observable, + mg: ModuleManager, +) { + return (pred: (i: BaseInteraction) => i is T) => + i.pipe( + filter(pred), + filterMap(event => { + const fullPath = mg.get(createId(event as unknown as Interaction)) + if(!fullPath) return err(); + return defaultModuleLoader(fullPath) + .then(res => res.map(module => ({ module, event }) )) + }), + map(createDispatcher) + ) +} +/** + * Creates a unique ID for a given interaction object. + * @param event The interaction object for which to create an ID. + * @returns A unique string ID based on the type and properties of the interaction object. + */ +function createId(event: T) { + let id: string; + switch(event.type) { + case InteractionType.MessageComponent: { + id = `${event.customId}__C${event.componentType}`; + } break; + case InteractionType.ApplicationCommand: + case InteractionType.ApplicationCommandAutocomplete: { + id = `${event.commandName}__A${event.commandType}`; + console.log(id) + } break; + case InteractionType.ModalSubmit: { + id = `${event.customId}__C1`; + } break; + } + return id; +} + +function createDispatcher({ + module, + event, +}: { + module: Processed; + event: BaseInteraction; +}) { + switch (module.type) { + case CommandType.Text: + throw Error(SernError.MismatchEvent+ " Found a text module in interaction stream."); + case CommandType.Slash: + case CommandType.Both: { + if (isAutocomplete(event)) { + /** + * Autocomplete is a special case that + * must be handled separately, since it's + * too different from regular command modules + */ + return dispatchAutocomplete(module as Processed, event); + } + return dispatchCommand(module, contextArgs(event as ChatInputCommandInteraction)); + } + default: + return dispatchCommand(module, interactionArg(event)); + } +} + +export function buildModules(path: string, sernEmitter: SernEmitter) { + return pipe( + switchMap(() => Files.buildModuleStream(path)), + errTap(error => { + sernEmitter.emit('module.register', SernEmitter.failure(undefined, error)); + }), + map(module => ({ module, absPath: module[sernMeta].fullPath })) + ); +} + diff --git a/src/handler/events/interactions.ts b/src/handler/events/interactions.ts index c357cda..5901f97 100644 --- a/src/handler/events/interactions.ts +++ b/src/handler/events/interactions.ts @@ -1,101 +1,22 @@ -import { ChatInputCommandInteraction, Interaction, InteractionType } from 'discord.js'; +import { Interaction } from 'discord.js'; import { catchError, concatMap, - EMPTY, - filter, finalize, - fromEvent, - map, - Observable, - of, - OperatorFunction, - pipe, + merge, } from 'rxjs'; -import { CommandType,SernError } from '../../core/structures'; -import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from './dispatchers'; +import { SernError } from '../../core/structures'; import { executeModule, makeModuleExecutor } from './observableHandling'; -import type { CommandModule } from '../../types/module'; import { ErrorHandling, handleError } from '../../core/contracts/errorHandling'; import { SernEmitter, WebsocketStrategy } from '../../core'; -import type { Processed } from '../../types/handler'; +import { sharedObservable } from '../../core/operators' import { useContainerRaw } from '../../core/dependencies'; import type { Logging, ModuleManager } from '../../core/contracts'; import type { EventEmitter } from 'node:events'; -import { ModuleGetter, createModuleGetter } from '../../core/contracts/moduleManager'; +import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates'; +import { createHandler } from './generic'; -function handleMessageComponents(i: Observable, mg: ModuleGetter) { - return i.pipe( - filter(e => e.isMessageComponent()), - map(event => ({ module: mg('' as any), event }) ) - ) -} - -function handleAutocomplete(i: Observable, mg: ModuleGetter) { - return i.pipe( - filter(e => e.isAutocomplete()), - map(event => ({ module: mg('' as any), event }) ) - ) -} - -function handleApplicationCommands(i: Observable, mg: ModuleGetter) { - return i.pipe( - filter(e => e.isCommand()), - map(event => ({ module: mg('' as any), event }) ) - ) -} - -function handleModal(i: Observable, mg: ModuleGetter) { - return i.pipe( - filter(e => e.isModalSubmit()), - map(event => ({ module: mg('' as any), event }) ) - ) -} -function makeInteractionProcessor( - modules: ModuleManager, -): OperatorFunction; event: Interaction }> { - const get = createModuleGetter(modules); - return pipe( - concatMap(event => { - switch(event.type) { - case InteractionType.MessageComponent: - case InteractionType.ModalSubmit: { - const id = `${event.customId}__M${event.componentType}` - } break; - case InteractionType.ApplicationCommand: - case InteractionType.ApplicationCommandAutocomplete: { - - } - - } - if (event.isMessageComponent()) { - const customId = event.customId; - const module = get(ms => { - return ms.InteractionHandlers[event.componentType].get(customId); - }); - return of({ module, event }); - } else if (event.isCommand() || event.isAutocomplete()) { - const commandName = event.commandName; - const module = get( - ms => - /** - * try to fetch from ApplicationCommands, if nothing, try BothCommands - * exists on the API but not sern - */ - ms.ApplicationCommands[event.commandType].get(commandName) ?? - ms.BothCommands.get(commandName), - ); - return of({ module, event }); - } else if (event.isModalSubmit()) { - const module = get(ms => ms.ModalSubmit.get(event.customId)); - return of({ module, event }); - } else return EMPTY; - }), - filter(m => m.module !== undefined), - ) as OperatorFunction; event: Interaction }>; -} - export function makeInteractionCreate([s, err, log, modules, client]: [ SernEmitter, ErrorHandling, @@ -105,13 +26,16 @@ export function makeInteractionCreate([s, err, log, modules, client]: [ ], platform: WebsocketStrategy ) { - //map. If nothing again,this means a slash command - const interactionStream$ = fromEvent(client, platform.eventNames[0]) as Observable; - const interactionProcessor = makeInteractionProcessor(modules); - return interactionStream$ + const interactionStream$ = sharedObservable(client, platform.eventNames[0]); + const handle = createHandler(interactionStream$, modules); + const interactionHandler$ = merge( + handle(isMessageComponent), + handle(isAutocomplete), + handle(isCommand), + handle(isModal) + ); + return interactionHandler$ .pipe( - interactionProcessor, - map(createDispatcher), makeModuleExecutor(module => { s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); }), @@ -119,7 +43,7 @@ export function makeInteractionCreate([s, err, log, modules, client]: [ catchError(handleError(err, log)), finalize(() => { log?.info({ - message: 'interactionCreate stream closed or reached end of lifetime', + message: 'interaction stream closed or reached end of lifetime', }); useContainerRaw() ?.disposeAll() @@ -128,31 +52,3 @@ export function makeInteractionCreate([s, err, log, modules, client]: [ ) .subscribe(); } - -function createDispatcher({ - module, - event, -}: { - event: Interaction; - module: Processed; -}) { - switch (module.type) { - case CommandType.Text: - throw Error(SernError.MismatchEvent); - case CommandType.Slash: - case CommandType.Both: { - if (event.isAutocomplete()) { - /** - * Autocomplete is a special case that - * must be handled separately, since it's - * too different from regular command modules - */ - return dispatchAutocomplete(module, event); - } else { - return dispatchCommand(module, contextArgs(event as ChatInputCommandInteraction)); - } - } - default: - return dispatchCommand(module, interactionArg(event)); - } -} diff --git a/src/handler/events/messages.ts b/src/handler/events/messages.ts index 8311d26..76e7579 100644 --- a/src/handler/events/messages.ts +++ b/src/handler/events/messages.ts @@ -1,5 +1,5 @@ -import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of, pipe } from 'rxjs'; -import { type ModuleStore, SernError } from '../../core/structures'; +import { catchError, concatMap, EMPTY, finalize, map, of, pipe } from 'rxjs'; +import { SernError } from '../../core/structures'; import type { Message } from 'discord.js'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import type { CommandModule } from '../../types/module'; @@ -11,7 +11,9 @@ import { useContainerRaw } from '../../core/dependencies'; import type { Logging, ModuleManager } from '../../core/contracts'; import type { EventEmitter } from 'node:events'; import { WebsocketStrategy } from '../../core'; -import { createModuleGetter } from '../../core/contracts/moduleManager'; +import { err } from '../../core/functions'; +import { defaultModuleLoader } from '../../core/module-loading'; +import { sharedObservable, filterMap } from '../../core/operators'; /** * Removes the first character(s) _[depending on prefix length]_ of the message @@ -34,25 +36,21 @@ export function fmt(msg: string, prefix: string): string[] { */ const createMessageProcessor = ( defaultPrefix: string, - get: ( - cb: (ms: ModuleStore) => Processed | undefined, - ) => CommandModule | undefined, + moduleManager: ModuleManager ) => pipe( ignoreNonBot(defaultPrefix), - //This concatMap checks if module is undefined, and if it is, do not continue. - // Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet - concatMap(message => { + filterMap(message => { const [prefix, ...rest] = fmt(message.content, defaultPrefix); - const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)); - if (module === undefined) { - return EMPTY; + const fullPath = moduleManager.get(`${prefix}__A0`); + if (fullPath === undefined) { + return err(); } - const payload = { - args: contextArgs(message, rest), - module, - }; - return of(payload); + return defaultModuleLoader(fullPath).then( + result => { + const args = contextArgs(message, rest); + return result.map(module => ({ module, args })) + }) }), map(({ args, module }) => dispatchCommand(module as Processed, args)), ); @@ -70,9 +68,8 @@ export function makeMessageCreate( if(!platform.defaultPrefix) { return EMPTY.subscribe() } - const get = createModuleGetter(modules); - const messageStream$ = fromEvent(client, platform.eventNames[1]) as Observable; - const messageProcessor = createMessageProcessor(platform.defaultPrefix, get); + const messageStream$ = sharedObservable(client, platform.eventNames[1]); + const messageProcessor = createMessageProcessor(platform.defaultPrefix, modules); return messageStream$ .pipe( messageProcessor, diff --git a/src/handler/events/ready.ts b/src/handler/events/ready.ts index eac4c92..c6cbe55 100644 --- a/src/handler/events/ready.ts +++ b/src/handler/events/ready.ts @@ -1,50 +1,27 @@ -import { Subscription, fromEvent, map, of, pipe, switchMap, take } from 'rxjs'; -import * as Files from '../../core/module-loading'; +import { Subscription, fromEvent, of, take } from 'rxjs'; import { callInitPlugins } from './observableHandling'; -import { CommandType, type ModuleStore, SernError } from '../../core/structures'; +import { CommandType, SernError } from '../../core/structures'; import { Result } from 'ts-results-es'; -import { ApplicationCommandType, ComponentType } from 'discord.js'; import type { CommandModule } from '../../types/module'; -import type { Processed } from '../../types/handler'; +import type { Processed, ServerlessDependencyList, WebsocketDependencyList } from '../../types/handler'; import type { ErrorHandling, Logging, ModuleManager } from '../../core/contracts'; -import { err, ok } from '../../core/functions'; -import { errTap, fillDefaults } from '../../core/operators'; import SernEmitter from '../../core/sernEmitter'; import type { EventEmitter } from 'node:events'; import { DispatchType, PlatformStrategy, ServerlessStrategy, WebsocketStrategy } from '../../core'; - -function buildCommandModules(commandDir: string, sernEmitter: SernEmitter) { - return pipe( - switchMap(() => Files.buildModuleStream(commandDir)), - errTap(error => { - sernEmitter.emit('module.register', SernEmitter.failure(undefined, error)); - }), - map(fillDefaults), - ); -} +import { sernMeta } from '../../commands'; +import { buildModules } from './generic'; /** * @overload */ export function makeReadyEvent( - dependencies: [ - SernEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, - ], + dependencies: ServerlessDependencyList, commandDir: string, platform: ServerlessStrategy ): Subscription export function makeReadyEvent( - dependencies: [ - SernEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, - EventEmitter - ], + dependencies: WebsocketDependencyList, commandDir: string, platform: WebsocketStrategy @@ -66,7 +43,7 @@ export function makeReadyEvent( : fromEvent(client!, platform.eventNames[2]).pipe(take(1)); return ready$ .pipe( - buildCommandModules(commandDir, sEmitter), + buildModules(commandDir, sEmitter), callInitPlugins({ onStop: module => { sEmitter.emit( @@ -90,49 +67,11 @@ export function makeReadyEvent( function registerModule>( manager: ModuleManager, - mod: T, + module: T, ): Result { - const name = mod.name; - const insert = (cb: (ms: ModuleStore) => void) => { - const set = Result.wrap(() => manager.set(cb)); - return set.ok ? ok() : err(); - }; - switch (mod.type) { - case CommandType.Text: { - mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); - return insert(ms => ms.TextCommands.set(name, mod)); - } - case CommandType.Slash: - return insert(ms => - ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod), - ); - case CommandType.Both: { - mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); - return insert(ms => ms.BothCommands.set(name, mod)); - } - case CommandType.CtxUser: - return insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod)); - case CommandType.CtxMsg: - return insert(ms => - ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod), - ); - case CommandType.Button: - return insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod)); - case CommandType.StringSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod)); - case CommandType.MentionableSelect: - return insert(ms => - ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod), - ); - case CommandType.UserSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod)); - case CommandType.ChannelSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.ChannelSelect].set(name, mod)); - case CommandType.RoleSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod)); - case CommandType.Modal: - return insert(ms => ms.ModalSubmit.set(name, mod)); - default: - return err(); + const { id, fullPath } = module[sernMeta]; + if(module.type === CommandType.Both || module.type === CommandType.Text) { + module.alias?.forEach(a => manager.set(`${a}__A0` , fullPath)) } + return Result.wrap(() => manager.set(id, fullPath)) } diff --git a/src/handler/events/userDefined.ts b/src/handler/events/userDefined.ts index cb94eec..69ef3c5 100644 --- a/src/handler/events/userDefined.ts +++ b/src/handler/events/userDefined.ts @@ -1,5 +1,4 @@ -import { catchError, finalize, map, mergeAll } from 'rxjs'; -import * as Files from '../../core/module-loading'; +import { catchError, finalize, map, mergeAll, of } from 'rxjs'; import type { Processed, WebsocketDependencies } from '../../types/handler'; import { callInitPlugins } from './observableHandling'; import type { CommandModule, EventModule } from '../../types/module'; @@ -9,9 +8,9 @@ import type { ErrorHandling, Logging } from '../../core/contracts'; import { SernError, EventType } from '../../core/structures'; import { eventDispatcher } from './dispatchers'; import { handleError } from '../../core/contracts/errorHandling'; -import { errTap, fillDefaults } from '../../core/operators'; import { useContainerRaw } from '../../core/dependencies'; import { AnyWrapper } from '../../core/structures/wrapper'; +import { buildModules } from './generic'; export function makeEventsHandler( [s, err, log, client]: [SernEmitter, ErrorHandling, Logging | undefined, EventEmitter], @@ -19,19 +18,6 @@ export function makeEventsHandler( containerGetter: AnyWrapper['containerConfig'], ) { const lazy = (k: string) => containerGetter.get(k as keyof WebsocketDependencies)[0]; - const eventStream$ = eventObservable(eventsPath, s); - - const eventCreation$ = eventStream$.pipe( - map(fillDefaults), - callInitPlugins({ - onStop: module => - s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), - onNext: ({ module }) => { - s.emit('module.register', SernEmitter.success(module)); - return module; - }, - }), - ); const intoDispatcher = (e: Processed) => { switch (e.type) { case EventType.Sern: @@ -46,30 +32,29 @@ export function makeEventsHandler( ); } }; - eventCreation$ - .pipe( - map(intoDispatcher), - /** - * Where all events are turned on - */ - mergeAll(), - catchError(handleError(err, log)), - finalize(() => { - log?.info({ message: 'an event module reached end of lifetime' }); - useContainerRaw() - ?.disposeAll() - .then(() => { - log?.info({ message: 'Cleaning container and crashing' }); - }); - }), - ) - .subscribe(); -} - -function eventObservable(events: string, emitter: SernEmitter) { - return Files.buildModuleStream(events).pipe( - errTap(reason => { - emitter.emit('module.register', SernEmitter.failure(undefined, reason)); + of(null).pipe( + buildModules(eventsPath, s), + callInitPlugins({ + onStop: module => + s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), + onNext: ({ module }) => { + s.emit('module.register', SernEmitter.success(module)); + return module; + }, }), - ); + map(intoDispatcher), + /** + * Where all events are turned on + */ + mergeAll(), + catchError(handleError(err, log)), + finalize(() => { + log?.info({ message: 'an event module reached end of lifetime' }); + useContainerRaw() + ?.disposeAll() + .then(() => { + log?.info({ message: 'Cleaning container and crashing' }); + }); + }), + ).subscribe(); } diff --git a/src/handler/sern.ts b/src/handler/sern.ts index 7e6a749..514124c 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -24,8 +24,8 @@ import { discordjs } from '../core'; * ``` */ export function init(wrapper: DefaultWrapper) { - const dependenciesAnd = makeFetcher(wrapper.containerConfig); const startTime = performance.now(); + const dependenciesAnd = makeFetcher(wrapper.containerConfig); const dependencies = dependenciesAnd(['@sern/modules', '@sern/client']); if (wrapper.events !== undefined) { makeEventsHandler(