diff --git a/CHANGELOG.md b/CHANGELOG.md index e390456..01b34c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [3.3.4](https://github.com/sern-handler/handler/compare/v3.3.3...v3.3.4) (2024-03-18) + + +### Bug Fixes + +* sern emitter err ([#358](https://github.com/sern-handler/handler/issues/358)) ([90e55df](https://github.com/sern-handler/handler/commit/90e55dfa1466c91e5da48922251309331921b1ef)) + +## [3.3.3](https://github.com/sern-handler/handler/compare/v3.3.2...v3.3.3) (2024-02-25) + + +### Bug Fixes + +* rm deprecated class modules, clean up, rm indirection ([#355](https://github.com/sern-handler/handler/issues/355)) ([48f9f6e](https://github.com/sern-handler/handler/commit/48f9f6ec16e650d574bd24dcbb0ed176933bfe17)) +* singleton init not being fired when inserting function ([07b11b3](https://github.com/sern-handler/handler/commit/07b11b357baac0c3c7055c022bc353995c80f766)) +* typings and cleanup ([#356](https://github.com/sern-handler/handler/issues/356)) ([ce8c4bf](https://github.com/sern-handler/handler/commit/ce8c4bf6492b9680fb1c1a530d3e0028f214ad2f)) + ## [3.3.2](https://github.com/sern-handler/handler/compare/v3.3.1...v3.3.2) (2024-01-08) diff --git a/package.json b/package.json index 26f45ae..a792359 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@sern/handler", "packageManager": "yarn@3.5.0", - "version": "3.3.2", + "version": "3.3.4", "description": "A complete, customizable, typesafe, & reactive framework for discord bots.", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/core/_internal.ts b/src/core/_internal.ts index 513abaa..1b2f35c 100644 --- a/src/core/_internal.ts +++ b/src/core/_internal.ts @@ -5,6 +5,6 @@ export * from './functions'; export type { VoidResult } from '../types/core-plugin'; export { SernError } from './structures/enums'; export { ModuleStore } from './structures/module-store'; -export * as DefaultServices from './structures/services'; -export { useContainerRaw } from './ioc/base' +export * as __Services from './structures/services'; +export { useContainerRaw } from './ioc/base'; diff --git a/src/core/contracts/module-manager.ts b/src/core/contracts/module-manager.ts index 624dac9..5dbeaee 100644 --- a/src/core/contracts/module-manager.ts +++ b/src/core/contracts/module-manager.ts @@ -16,12 +16,19 @@ interface MetadataAccess { * @internal - direct access to the module manager will be removed in version 4 */ export interface ModuleManager extends MetadataAccess { - get(id: string): string | undefined; + get(id: string): Module | undefined; - set(id: string, path: string): void; - getPublishableCommands(): Promise; + set(id: string, path: Module): void; + /** + * @deprecated + */ + getPublishableCommands(): CommandModule[]; + + /* + * @deprecated + */ getByNameCommandType( name: string, commandType: T, - ): Promise | undefined; + ): CommandModuleDefs[T] | undefined; } diff --git a/src/core/contracts/module-store.ts b/src/core/contracts/module-store.ts index b6157b6..90818f8 100644 --- a/src/core/contracts/module-store.ts +++ b/src/core/contracts/module-store.ts @@ -4,6 +4,6 @@ import type { CommandMeta, Module } from '../../types/core-modules'; * Represents a core module store that stores IDs mapped to file paths. */ export interface CoreModuleStore { - commands: Map; + commands: Map; metadata: WeakMap; } diff --git a/src/core/functions.ts b/src/core/functions.ts index 60d62e9..878151b 100644 --- a/src/core/functions.ts +++ b/src/core/functions.ts @@ -10,10 +10,10 @@ import type { UserContextMenuCommandInteraction, AutocompleteInteraction } from 'discord.js'; -import { ApplicationCommandOptionType, InteractionType } from 'discord.js' +import { ApplicationCommandOptionType, InteractionType } from 'discord.js'; import { PayloadType, PluginType } from './structures'; import assert from 'assert'; -import { Payload } from '../types/utility'; +import type { Payload } from '../types/utility'; //function wrappers for empty ok / err export const ok = /* @__PURE__*/ () => Ok.EMPTY; @@ -50,7 +50,7 @@ export function treeSearch( if (options === undefined) return undefined; //clone to prevent mutation of original command module const _options = options.map(a => ({ ...a })); - let subcommands = new Set(); + const subcommands = new Set(); while (_options.length > 0) { const cur = _options.pop()!; switch (cur.type) { diff --git a/src/core/id.ts b/src/core/id.ts index 28032c5..50a781e 100644 --- a/src/core/id.ts +++ b/src/core/id.ts @@ -42,19 +42,10 @@ const TypeMap = new Map([ [CommandType.RoleSelect, ComponentType.RoleSelect], [CommandType.ChannelSelect, ComponentType.ChannelSelect]]); -/* - * Generates a number based on CommandType. - * This corresponds to an ApplicationCommandType or ComponentType - * TextCommands are 0 as they aren't either or. - */ -function apiType(t: CommandType | EventType) { - return TypeMap.get(t)!; -} - /* * Generates an id based on name and CommandType. * A is for any ApplicationCommand. C is for any ComponentCommand - * Then, another number generated by apiType function is appended + * Then, another number fetched from TypeMap */ export function create(name: string, type: CommandType | EventType) { if(type == CommandType.Text) { @@ -67,7 +58,7 @@ export function create(name: string, type: CommandType | EventType) { return `${name}_M`; } const am = (appBitField & type) !== 0 ? 'A' : 'C'; - return `${name}_${am}${apiType(type)}` + return `${name}_${am}${TypeMap.get(type)!}` } diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index 540204a..5994783 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -3,18 +3,38 @@ import { useContainer } from './dependency-injection'; import type { CoreDependencies, DependencyConfiguration } from '../../types/ioc'; import { CoreContainer } from './container'; import { Result } from 'ts-results-es'; -import { DefaultServices } from '../_internal'; +import { __Services } from '../_internal'; import { AnyFunction } from '../../types/utility'; import type { Logging } from '../contracts/logging'; - +import type { UnpackFunction } from 'iti'; //SIDE EFFECT: GLOBAL DI let containerSubject: CoreContainer>; /** - * @deprecated + * @internal + * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything + * then it will swap + */ +export async function __swap_container(c: CoreContainer>) { + if(containerSubject) { + await containerSubject.disposeAll() + } + containerSubject = c; +} + +/** + * @internal + * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything + * then it will swap + */ +export function __add_container(key: string,v : Insertable) { + containerSubject.add({ [key]: v }); +} + +/** * Returns the underlying data structure holding all dependencies. * Exposes methods from iti - * Use the Service API. The container should be readonly + * Use the Service API. The container should be readonly from the consumer side */ export function useContainerRaw() { assert.ok( @@ -29,19 +49,27 @@ export function disposeAll(logger: Logging|undefined) { ?.disposeAll() .then(() => logger?.info({ message: 'Cleaning container and crashing' })); } - -const dependencyBuilder = (container: any, excluded: string[] ) => { - type Insertable = - | ((container: CoreContainer) => unknown ) +type UnpackedDependencies = { + [K in keyof Dependencies]: UnpackFunction +} +type Insertable = + | ((container: UnpackedDependencies) => unknown) | object +const dependencyBuilder = (container: any, excluded: string[] ) => { return { /** * Insert a dependency into your container. * Supply the correct key and dependency */ add(key: keyof Dependencies, v: Insertable) { - Result.wrap(() => container.add({ [key]: v})) - .expect("Failed to add " + key); + if(typeof v !== 'function') { + Result.wrap(() => container.add({ [key]: v})) + .expect("Failed to add " + key); + } else { + Result.wrap(() => + container.add((cntr: UnpackedDependencies) => ({ [key]: v(cntr)} ))) + .expect("Failed to add " + key); + } }, /** * Exclude any dependencies from being added. @@ -57,8 +85,14 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { * Swap out a preexisting dependency. */ swap(key: keyof Dependencies, v: Insertable) { - Result.wrap(() => container.upsert({ [key]: v })) - .expect("Failed to update " + key); + if(typeof v !== 'function') { + Result.wrap(() => container.upsert({ [key]: v})) + .expect("Failed to update " + key); + } else { + Result.wrap(() => + container.upsert((cntr: UnpackedDependencies) => ({ [key]: v(cntr)}))) + .expect("Failed to update " + key); + } }, /** * @param key the key of the dependency @@ -76,17 +110,11 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { }; }; -type CallbackBuilder = (c: ReturnType) => any type ValidDependencyConfig = - | CallbackBuilder + | ((c: ReturnType) => any) | DependencyConfiguration; -export const insertLogger = (containerSubject: CoreContainer) => { - containerSubject - .upsert({'@sern/logger': () => new DefaultServices.DefaultLogging}); -} - /** * Given the user's conf, check for any excluded/included dependency keys. @@ -101,7 +129,7 @@ function composeRoot( //container should have no client or logger yet. const hasLogger = conf.exclude?.has('@sern/logger'); if (!hasLogger) { - insertLogger(container); + __add_container('@sern/logger', new __Services.DefaultLogging); } //Build the container based on the callback provided by the user conf.build(container as CoreContainer>); @@ -119,13 +147,13 @@ export async function makeDependencies if(typeof conf === 'function') { const excluded: string[] = []; conf(dependencyBuilder(containerSubject, excluded)); - + //We only include logger if it does not exist const includeLogger = !excluded.includes('@sern/logger') - && !containerSubject.getTokens()['@sern/logger']; + && !containerSubject.hasKey('@sern/logger'); if(includeLogger) { - insertLogger(containerSubject); + __add_container('@sern/logger', new __Services.DefaultLogging); } containerSubject.ready(); diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index 638eb8f..e8c4970 100644 --- a/src/core/ioc/container.ts +++ b/src/core/ioc/container.ts @@ -2,7 +2,7 @@ import { Container } from 'iti'; import { Disposable } from '../'; import * as assert from 'node:assert'; import { Subject } from 'rxjs'; -import { DefaultServices, ModuleStore } from '../_internal'; +import { __Services, ModuleStore } from '../_internal'; import * as Hooks from './hooks'; import { EventEmitter } from 'node:events'; @@ -23,12 +23,11 @@ export class CoreContainer> extends Container) - .add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling, + .add({ '@sern/errors': () => new __Services.DefaultErrorHandling, '@sern/emitter': () => new EventEmitter({ captureRejections: true }), '@sern/store': () => new ModuleStore }) .add(ctx => { - return { '@sern/modules': () => - new DefaultServices.DefaultModuleManager(ctx['@sern/store']) }; + return { '@sern/modules': new __Services.DefaultModuleManager(ctx['@sern/store'])}; }); } @@ -52,8 +51,6 @@ export class CoreContainer> extends Container(absPath: string) { if ('default' in commandModule ) { commandModule = commandModule.default; } - return Result - .wrap(() => ({ module: commandModule.getInstance() })) - .unwrapOr({ module: commandModule }) as T; + return { module: commandModule } as T; } export async function defaultModuleLoader(absPath: string): ModuleResult { @@ -106,7 +103,7 @@ async function* readPaths(dir: string): AsyncGenerator { } } -export const requir = createRequire(import.meta.url); +const requir = createRequire(import.meta.url); export function loadConfig(wrapper: Wrapper | 'file', log: Logging | undefined): Wrapper { if (wrapper !== 'file') { diff --git a/src/core/modules.ts b/src/core/modules.ts index ccfbe56..5b1e541 100644 --- a/src/core/modules.ts +++ b/src/core/modules.ts @@ -1,19 +1,13 @@ import { ClientEvents } from 'discord.js'; -import { CommandType, EventType, PluginType } from '../core/structures'; +import { EventType } from '../core/structures'; import type { - AnyCommandPlugin, AnyEventPlugin, - CommandArgs, - ControlPlugin, - EventArgs, - InitPlugin, } from '../types/core-plugin'; import type { CommandModule, EventModule, InputCommand, InputEvent, - Module, } from '../types/core-modules'; import { partitionPlugins } from './_internal'; import type { Awaitable } from '../types/utility'; @@ -61,53 +55,3 @@ export function discordEvent(mod: { }); } - -/** - * @deprecated - */ -function prepareClassPlugins(c: Module) { - const [onEvent, initPlugins] = partitionPlugins(c.plugins); - c.plugins = initPlugins as InitPlugin[]; - c.onEvent = onEvent as ControlPlugin[]; -} - -/** - * @deprecated - * Will be removed in future - */ -export abstract class CommandExecutable { - abstract type: Type; - plugins: AnyCommandPlugin[] = []; - private static _instance: CommandModule; - - static getInstance() { - if (!CommandExecutable._instance) { - //@ts-ignore - CommandExecutable._instance = new this(); - prepareClassPlugins(CommandExecutable._instance); - } - return CommandExecutable._instance; - } - - abstract execute(...args: CommandArgs): Awaitable; -} - -/** - * @deprecated - * Will be removed in future - */ -export abstract class EventExecutable { - abstract type: Type; - plugins: AnyEventPlugin[] = []; - - private static _instance: EventModule; - static getInstance() { - if (!EventExecutable._instance) { - //@ts-ignore - EventExecutable._instance = new this(); - prepareClassPlugins(EventExecutable._instance); - } - return EventExecutable._instance; - } - abstract execute(...args: EventArgs): Awaitable; -} diff --git a/src/core/operators.ts b/src/core/operators.ts index d534c5b..4c5ff72 100644 --- a/src/core/operators.ts +++ b/src/core/operators.ts @@ -59,13 +59,14 @@ export const sharedEventStream = (e: Emitter, eventName: string) => { return (fromEvent(e, eventName) as Observable).pipe(share()); }; -export function handleError(crashHandler: ErrorHandling, logging?: Logging) { +export function handleError(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) { return (pload: unknown, caught: Observable) => { // This is done to fit the ErrorHandling contract - const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true })); - //formatted payload - logging?.error({ message: util.inspect(pload) }); - crashHandler.updateAlive(err); + if(!emitter.emit('error', pload)) { + const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true })); + logging?.error({ message: util.inspect(pload) }); + crashHandler.updateAlive(err); + } return caught; }; } diff --git a/src/core/presences.ts b/src/core/presences.ts index c587575..594c836 100644 --- a/src/core/presences.ts +++ b/src/core/presences.ts @@ -25,10 +25,8 @@ export type Config = * Create a Presence module which **MUST** be put in a file called presence.(language-extension) * adjacent to the file where **Sern.init** is CALLED. */ -export function module -(conf: Config) { - return conf; -} +export function module(conf: Config) +{ return conf; } /** diff --git a/src/core/structures/module-store.ts b/src/core/structures/module-store.ts index 44d9ca1..49b22c5 100644 --- a/src/core/structures/module-store.ts +++ b/src/core/structures/module-store.ts @@ -7,5 +7,5 @@ import { CommandMeta, Module } from '../../types/core-modules'; */ export class ModuleStore { metadata = new WeakMap(); - commands = new Map(); + commands = new Map(); } diff --git a/src/core/structures/services/error-handling.ts b/src/core/structures/services/error-handling.ts index 3f55b16..143639c 100644 --- a/src/core/structures/services/error-handling.ts +++ b/src/core/structures/services/error-handling.ts @@ -10,7 +10,7 @@ export class DefaultErrorHandling implements ErrorHandling { throw err; } - #keepAlive = 5; + #keepAlive = 1; updateAlive(err: Error) { this.#keepAlive--; diff --git a/src/core/structures/services/module-manager.ts b/src/core/structures/services/module-manager.ts index 356936b..dad2888 100644 --- a/src/core/structures/services/module-manager.ts +++ b/src/core/structures/services/module-manager.ts @@ -1,6 +1,5 @@ import * as Id from '../../../core/id'; import { CoreModuleStore, ModuleManager } from '../../contracts'; -import { Files } from '../../_internal'; import { CommandMeta, CommandModule, CommandModuleDefs, Module } from '../../../types/core-modules'; import { CommandType } from '../enums'; /** @@ -13,11 +12,11 @@ export class DefaultModuleManager implements ModuleManager { getByNameCommandType(name: string, commandType: T) { - const id = this.get(Id.create(name, commandType)); - if (!id) { + const module = this.get(Id.create(name, commandType)); + if (!module) { return undefined; } - return Files.importModule(id); + return module as CommandModuleDefs[T]; } setMetadata(m: Module, c: CommandMeta): void { @@ -35,20 +34,18 @@ export class DefaultModuleManager implements ModuleManager { get(id: string) { return this.moduleStore.commands.get(id); } - set(id: string, path: string): void { + set(id: string, path: CommandModule): void { this.moduleStore.commands.set(id, path); } //not tested - getPublishableCommands(): Promise { + getPublishableCommands(): CommandModule[] { const entries = this.moduleStore.commands.entries(); const publishable = 0b000000110; - return Promise.all( - Array.from(entries) + return Array.from(entries) .filter(([id]) => { const last_entry = id.at(-1); return last_entry == 'B' || !(publishable & Number.parseInt(last_entry!)); }) - .map(([, path]) => Files.importModule(path)), - ); + .map(([, path]) => path as CommandModule); } } diff --git a/src/handlers/dispatchers.ts b/src/handlers/dispatchers.ts index d52f7be..8e4eb10 100644 --- a/src/handlers/dispatchers.ts +++ b/src/handlers/dispatchers.ts @@ -17,10 +17,7 @@ import type { CommandModule, Module, Processed } from '../types/core-modules'; //TODO: refactor dispatchers so that it implements a strategy for each different type of payload? export function dispatchMessage(module: Processed, args: [Context, Args]) { - return { - module, - args, - }; + return { module, args }; } export function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[]) { @@ -87,9 +84,6 @@ export function createDispatcher(payload: { } return { module: payload.module, args: contextArgs(payload.event) }; } - default: return { - module: payload.module, - args: [payload.event], - }; + default: return { module: payload.module, args: [payload.event] }; } } diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index a2e757b..fc7a16c 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -8,9 +8,9 @@ import { of, throwError, tap, - MonoTypeOperatorFunction, catchError, finalize, + map, } from 'rxjs'; import { Files, @@ -29,8 +29,7 @@ import { ObservableInput, pipe } from 'rxjs'; import { Err, Ok, Result } from 'ts-results-es'; import type { Awaitable } from '../types/utility'; import type { ControlPlugin } from '../types/core-plugin'; -import type { AnyModule, CommandModule, Module, Processed } from '../types/core-modules'; -import type { ImportPayload } from '../types/core'; +import type { AnyModule, CommandMeta, CommandModule, Module, Processed } from '../types/core-modules'; import { disposeAll } from '../core/ioc/base'; function createGenericHandler( @@ -74,18 +73,13 @@ export function createInteractionHandler( const possibleIds = Id.reconstruct(event); let fullPaths= possibleIds .map(id => mg.get(id)) - .filter((id): id is string => id !== undefined); + .filter((id): id is Module => id !== undefined); if(fullPaths.length == 0) { return Err.EMPTY; } const [ path ] = fullPaths; - return Files - .defaultModuleLoader>(path) - .then(payload => Ok(createDispatcher({ - module: payload.module, - event, - }))); + return Ok(createDispatcher({ module: path as Processed, event })); }); } @@ -103,39 +97,37 @@ export function createMessageHandler( return Err('Possibly undefined behavior: could not find a static id to resolve'); } } - return Files - .defaultModuleLoader>(fullPath) - .then(payload => { - const args = contextArgs(event, rest); - return Ok({ args, ...payload }); - }); + return Ok({ args: contextArgs(event, rest), module: fullPath as Processed }) }); } /** - * IMPURE SIDE EFFECT * This function assigns remaining, incomplete data to each imported module. */ -function assignDefaults( - moduleManager: ModuleManager, -): MonoTypeOperatorFunction> { - return tap(({ module, absPath }) => { - module.name ??= Files.filename(absPath); - module.description ??= '...'; - moduleManager.setMetadata(module, { - isClass: module.constructor.name === 'Function', - fullPath: absPath, - id: Id.create(module.name, module.type), - }); +function assignDefaults() { + return map(({ module, absPath }) => { + const processed = { + name: module.name ?? Files.filename(absPath), + description: module.description ?? '...', + ...module + } + return { + module: processed, + absPath, + metadata: { + isClass: module.constructor.name === 'Function', + fullPath: absPath, + id: Id.create(processed.name, module.type), + } + } }); } export function buildModules( input: ObservableInput, - moduleManager: ModuleManager, ) { return Files .buildModuleStream>(input) - .pipe(assignDefaults(moduleManager)); + .pipe(assignDefaults()); } @@ -219,9 +211,9 @@ export function callInitPlugins>(sernEmitter: Emi onStop: (module: T) => { sernEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); }, - onNext: ({ module }) => { - sernEmitter.emit('module.register', resultPayload(PayloadType.Success, module)); - return { module }; + onNext: (payload) => { + sernEmitter.emit('module.register', resultPayload(PayloadType.Success, payload.module)); + return payload as { module: T; metadata: CommandMeta }; }, }), ); @@ -254,9 +246,9 @@ export function makeModuleExecutor< ); } -export const handleCrash = (err: ErrorHandling, log?: Logging) => +export const handleCrash = (err: ErrorHandling,sernemitter: Emitter, log?: Logging) => pipe( - catchError(handleError(err, log)), + catchError(handleError(err, sernemitter, log)), finalize(() => { log?.info({ message: 'A stream closed or reached end of lifetime', diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index 80c6ad0..e1ddb8e 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -1,29 +1,31 @@ -import { ObservableInput, concat, first, fromEvent, ignoreElements, pipe } from 'rxjs'; +import { ObservableInput, concat, first, fromEvent, ignoreElements, pipe, tap } from 'rxjs'; import { CommandType } from '../core/structures'; import { SernError } from '../core/_internal'; import { Result } from 'ts-results-es'; -import { ModuleManager } from '../core/contracts'; +import { Logging, ModuleManager } from '../core/contracts'; import { buildModules, callInitPlugins } from './_internal'; import * as assert from 'node:assert'; import * as util from 'node:util'; import type { DependencyList } from '../types/ioc'; -import type { AnyModule, Processed } from '../types/core-modules'; +import type { AnyModule, CommandMeta, Processed } from '../types/core-modules'; export function readyHandler( - [sEmitter, , , moduleManager, client]: DependencyList, + [sEmitter, , log , moduleManager, client]: DependencyList, allPaths: ObservableInput, ) { - const ready$ = fromEvent(client!, 'ready').pipe(once()); - - return concat(ready$, buildModules(allPaths, moduleManager)) + //Todo: add module manager on on ready + const ready$ = fromEvent(client!, 'ready').pipe(once(log)); + + return concat(ready$, buildModules(allPaths)) .pipe(callInitPlugins(sEmitter)) - .subscribe(({ module }) => { - register(moduleManager, module) + .subscribe(({ module, metadata }) => { + register(moduleManager, module, metadata) .expect(SernError.InvalidModuleType + ' ' + util.inspect(module)); }); } -const once = () => pipe( +const once = (log: Logging | undefined) => pipe( + tap(() => { log?.info({ message: "Waiting on discord client to be ready..." }) }), first(), ignoreElements()) @@ -31,20 +33,22 @@ const once = () => pipe( function register>( manager: ModuleManager, module: T, + metadata:CommandMeta ): Result { - const { id, fullPath } = manager.getMetadata(module)!; + manager.setMetadata(module, metadata)!; const validModuleType = module.type >= 0 && module.type <= 1 << 10; assert.ok( validModuleType, - `Found ${module.name} at ${fullPath}, which does not have a valid type`, + //@ts-ignore + `Found ${module.name} at ${metadata.fullPath}, which does not have a valid type`, ); if (module.type === CommandType.Both) { - module.alias?.forEach(a => manager.set(`${a}_B`, fullPath)); + module.alias?.forEach(a => manager.set(`${a}_B`, module)); } else { if(module.type === CommandType.Text){ - module.alias?.forEach(a => manager.set(`${a}_T`, fullPath)); + module.alias?.forEach(a => manager.set(`${a}_T`, module)); } } - return Result.wrap(() => manager.set(id, fullPath)); + return Result.wrap(() => manager.set(metadata.id, module)); } diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts index 95d6b4c..66e7dfb 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -23,7 +23,7 @@ export function eventsHandler( throw Error(SernError.InvalidModuleType + ' while creating event handler'); } }; - buildModules(allPaths, moduleManager) + buildModules(allPaths) .pipe( callInitPlugins(emitter), map(intoDispatcher), @@ -31,6 +31,6 @@ export function eventsHandler( * Where all events are turned on */ mergeAll(), - handleCrash(err, log)) + handleCrash(err, emitter, log)) .subscribe(); } diff --git a/src/index.ts b/src/index.ts index dd9bec6..1f0af39 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,12 +46,8 @@ export { commandModule, eventModule, discordEvent, - EventExecutable, - CommandExecutable, } from './core/modules'; export * as Presence from './core/presences' -export { - useContainerRaw -} from './core/_internal' + diff --git a/src/sern.ts b/src/sern.ts index e633be0..fd458f4 100644 --- a/src/sern.ts +++ b/src/sern.ts @@ -43,9 +43,10 @@ export function init(maybeWrapper: Wrapper | 'file') { //Ready event: load all modules and when finished, time should be taken and logged readyHandler(dependencies, Files.getFullPathTree(wrapper.commands)) .add(() => { + logger?.info({ message: "Client signaled ready, registering modules" }); const time = ((performance.now() - startTime) / 1000).toFixed(2); dependencies[0].emit('modulesLoaded'); - logger?.info({ message: `sern: registered all modules in ${time} s`, }); + logger?.info({ message: `sern: registered in ${time} s`, }); if(presencePath.exists) { const setPresence = async (p: any) => { return (dependencies[4] as Client).user?.setPresence(p); @@ -57,5 +58,5 @@ export function init(maybeWrapper: Wrapper | 'file') { const messages$ = messageHandler(dependencies, wrapper.defaultPrefix); const interactions$ = interactionHandler(dependencies); // listening to the message stream and interaction stream - merge(messages$, interactions$).pipe(handleCrash(errorHandler, logger)).subscribe(); + merge(messages$, interactions$).pipe(handleCrash(errorHandler, dependencies[0], logger)).subscribe(); } diff --git a/src/types/utility.ts b/src/types/utility.ts index 65efc3d..90339e4 100644 --- a/src/types/utility.ts +++ b/src/types/utility.ts @@ -18,7 +18,7 @@ export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; export interface SernEventsMapping { 'module.register': [Payload]; 'module.activate': [Payload]; - error: [Payload]; + error: [{ type: PayloadType.Failure; module?: AnyModule; reason: string | Error }]; warning: [Payload]; modulesLoaded: [never?]; } @@ -26,7 +26,7 @@ export interface SernEventsMapping { export type Payload = | { type: PayloadType.Success; module: AnyModule } | { type: PayloadType.Failure; module?: AnyModule; reason: string | Error } - | { type: PayloadType.Warning; reason: string }; + | { type: PayloadType.Warning; module: undefined; reason: string }; export type ReplyOptions = string | Omit | MessageReplyOptions; diff --git a/test/core/ioc.test.ts b/test/core/ioc.test.ts index de8015c..e790307 100644 --- a/test/core/ioc.test.ts +++ b/test/core/ioc.test.ts @@ -98,4 +98,16 @@ describe('ioc container', () => { container.ready(); expect(dependency.init).toHaveBeenCalledTimes(1); }) + + it('should detect a key already exists', () => { + container.add({ '@sern/client': dependency2 }); + expect(container.hasKey('@sern/client')).toBeTruthy() + }) + + + it('should detect a key already exists', () => { + container.add({ '@sern/client': () => dependency2 }); + expect(container.hasKey('@sern/client')).toBeTruthy() + }) + });