From ac7f47c59077124a3120da06960e85fb10372778 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 12 Feb 2024 21:47:35 -0600 Subject: [PATCH 01/10] refactor: rm deprecations, clean up, rm indirection --- src/core/contracts/module-manager.ts | 8 +-- src/core/contracts/module-store.ts | 2 +- src/core/ioc/base.ts | 11 ++++ src/core/ioc/container.ts | 3 +- src/core/modules.ts | 50 ----------------- src/core/structures/module-store.ts | 2 +- .../structures/services/module-manager.ts | 17 +++--- src/handlers/dispatchers.ts | 5 +- src/handlers/event-utils.ts | 53 +++++++++---------- src/handlers/ready-event.ts | 19 ++++--- src/index.ts | 6 +-- 11 files changed, 62 insertions(+), 114 deletions(-) diff --git a/src/core/contracts/module-manager.ts b/src/core/contracts/module-manager.ts index 624dac9..dca3dad 100644 --- a/src/core/contracts/module-manager.ts +++ b/src/core/contracts/module-manager.ts @@ -16,12 +16,12 @@ 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; + getPublishableCommands(): CommandModule[]; 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/ioc/base.ts b/src/core/ioc/base.ts index 540204a..eb26c9b 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -10,6 +10,17 @@ import type { Logging } from '../contracts/logging'; //SIDE EFFECT: GLOBAL DI let containerSubject: CoreContainer>; +/** + * @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; +} /** * @deprecated * Returns the underlying data structure holding all dependencies. diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index 638eb8f..f8ef276 100644 --- a/src/core/ioc/container.ts +++ b/src/core/ioc/container.ts @@ -27,8 +27,7 @@ export class CoreContainer> extends Container new EventEmitter({ captureRejections: true }), '@sern/store': () => new ModuleStore }) .add(ctx => { - return { '@sern/modules': () => - new DefaultServices.DefaultModuleManager(ctx['@sern/store']) }; + return { '@sern/modules': new DefaultServices.DefaultModuleManager(ctx['@sern/store'])}; }); } diff --git a/src/core/modules.ts b/src/core/modules.ts index ccfbe56..7ac112f 100644 --- a/src/core/modules.ts +++ b/src/core/modules.ts @@ -61,53 +61,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/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/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..1c93b3c 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[]) { diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index a2e757b..3954e59 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -11,6 +11,7 @@ import { MonoTypeOperatorFunction, catchError, finalize, + map, } from 'rxjs'; import { Files, @@ -74,18 +75,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,29 +99,28 @@ 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), + } + } }); } @@ -135,7 +130,7 @@ export function buildModules( ) { return Files .buildModuleStream>(input) - .pipe(assignDefaults(moduleManager)); + .pipe(assignDefaults()); } @@ -219,9 +214,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; }, }), ); diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index 80c6ad0..3eeb9f8 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -7,7 +7,7 @@ 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, @@ -17,8 +17,8 @@ export function readyHandler( return concat(ready$, buildModules(allPaths, moduleManager)) .pipe(callInitPlugins(sEmitter)) - .subscribe(({ module }) => { - register(moduleManager, module) + .subscribe(({ module, metadata }) => { + register(moduleManager, module, metadata) .expect(SernError.InvalidModuleType + ' ' + util.inspect(module)); }); } @@ -31,20 +31,23 @@ const once = () => pipe( function register>( manager: ModuleManager, module: T, + metadata: unknown ): Result { - const { id, fullPath } = manager.getMetadata(module)!; + manager.setMetadata(module, metadata as CommandMeta)!; 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)); + //@ts-ignore + return Result.wrap(() => manager.set(metadata.id, module)); } 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' + From 07b11b357baac0c3c7055c022bc353995c80f766 Mon Sep 17 00:00:00 2001 From: jacob Date: Wed, 14 Feb 2024 15:58:50 -0600 Subject: [PATCH 02/10] fix: singleton init not being fired when inserting function --- src/core/id.ts | 13 ++----------- src/core/ioc/base.ts | 21 ++++++++++++++++----- src/core/ioc/container.ts | 2 -- src/core/modules.ts | 8 +------- src/handlers/event-utils.ts | 7 +++---- src/handlers/ready-event.ts | 20 +++++++++++--------- src/handlers/user-defined-events.ts | 2 +- src/sern.ts | 3 ++- 8 files changed, 36 insertions(+), 40 deletions(-) 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 eb26c9b..493a8d6 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -22,7 +22,6 @@ export async function __swap_container(c: CoreContainer>) containerSubject = c; } /** - * @deprecated * Returns the underlying data structure holding all dependencies. * Exposes methods from iti * Use the Service API. The container should be readonly @@ -51,8 +50,14 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { * 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: CoreContainer) => ({ [key]: v(cntr)} ))) + .expect("Failed to add " + key); + } }, /** * Exclude any dependencies from being added. @@ -68,8 +73,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: CoreContainer) => ({ [key]: v(cntr)}))) + .expect("Failed to update " + key); + } }, /** * @param key the key of the dependency diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index f8ef276..4eef072 100644 --- a/src/core/ioc/container.ts +++ b/src/core/ioc/container.ts @@ -51,8 +51,6 @@ export class CoreContainer> extends Container() { +function assignDefaults() { return map(({ module, absPath }) => { const processed = { name: module.name ?? Files.filename(absPath), @@ -126,7 +126,6 @@ function assignDefaults() { export function buildModules( input: ObservableInput, - moduleManager: ModuleManager, ) { return Files .buildModuleStream>(input) @@ -216,7 +215,7 @@ export function callInitPlugins>(sernEmitter: Emi }, onNext: (payload) => { sernEmitter.emit('module.register', resultPayload(PayloadType.Success, payload.module)); - return payload; + return payload as { module: T; metadata: CommandMeta }; }, }), ); diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index 3eeb9f8..60739b1 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -1,8 +1,8 @@ -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'; @@ -10,20 +10,23 @@ import type { DependencyList } from '../types/ioc'; 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()); + const ready$ = fromEvent(client!, 'ready').pipe(once(log)); - return concat(ready$, buildModules(allPaths, moduleManager)) + return concat(ready$, buildModules(allPaths)) .pipe(callInitPlugins(sEmitter)) .subscribe(({ module, metadata }) => { register(moduleManager, module, metadata) .expect(SernError.InvalidModuleType + ' ' + util.inspect(module)); + //TODO: TEST ALL MODULES AGAIN + console.log(module) }); } -const once = () => pipe( +const once = (log: Logging | undefined) => pipe( + tap(() => { log?.info({ message: "Waiting on discord client to be ready..." }) }), first(), ignoreElements()) @@ -31,9 +34,9 @@ const once = () => pipe( function register>( manager: ModuleManager, module: T, - metadata: unknown + metadata:CommandMeta ): Result { - manager.setMetadata(module, metadata as CommandMeta)!; + manager.setMetadata(module, metadata)!; const validModuleType = module.type >= 0 && module.type <= 1 << 10; assert.ok( @@ -48,6 +51,5 @@ function register>( module.alias?.forEach(a => manager.set(`${a}_T`, module)); } } - //@ts-ignore 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..3dd4989 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), diff --git a/src/sern.ts b/src/sern.ts index e633be0..e88bd06 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); From 4efdbb21fbfa515593a40d1768155653cf8eec49 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 15 Feb 2024 12:26:34 -0600 Subject: [PATCH 03/10] refactor and generic internal add --- src/core/_internal.ts | 4 ++-- src/core/functions.ts | 6 +++--- src/core/ioc/base.ts | 30 +++++++++++++++++------------- src/core/ioc/container.ts | 6 +++--- src/handlers/event-utils.ts | 2 -- src/handlers/ready-event.ts | 5 ++--- test/core/ioc.test.ts | 12 ++++++++++++ 7 files changed, 39 insertions(+), 26 deletions(-) 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/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/ioc/base.ts b/src/core/ioc/base.ts index 493a8d6..3fd8c39 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -21,6 +21,16 @@ export async function __swap_container(c: CoreContainer>) } 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 @@ -39,11 +49,10 @@ 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 Insertable = + | ((container: CoreContainer) => unknown) | object +const dependencyBuilder = (container: any, excluded: string[] ) => { return { /** * Insert a dependency into your container. @@ -104,11 +113,6 @@ type ValidDependencyConfig = | CallbackBuilder | 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. @@ -123,7 +127,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 DefaultServices.DefaultLogging); } //Build the container based on the callback provided by the user conf.build(container as CoreContainer>); @@ -141,13 +145,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 DefaultServices.DefaultLogging); } containerSubject.ready(); diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index 4eef072..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,11 +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'])}; }); } diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index 7703923..643ab35 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -8,7 +8,6 @@ import { of, throwError, tap, - MonoTypeOperatorFunction, catchError, finalize, map, @@ -31,7 +30,6 @@ import { Err, Ok, Result } from 'ts-results-es'; import type { Awaitable } from '../types/utility'; import type { ControlPlugin } from '../types/core-plugin'; import type { AnyModule, CommandMeta, CommandModule, Module, Processed } from '../types/core-modules'; -import type { ImportPayload } from '../types/core'; import { disposeAll } from '../core/ioc/base'; function createGenericHandler( diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index 60739b1..e1ddb8e 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -13,15 +13,14 @@ export function readyHandler( [sEmitter, , log , moduleManager, client]: DependencyList, allPaths: ObservableInput, ) { + //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, metadata }) => { register(moduleManager, module, metadata) .expect(SernError.InvalidModuleType + ' ' + util.inspect(module)); - //TODO: TEST ALL MODULES AGAIN - console.log(module) }); } 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() + }) + }); From 86ebb221ed0a5361936f00a7acdd3f6ceee75c31 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 15 Feb 2024 12:36:00 -0600 Subject: [PATCH 04/10] deprecate a few things that i impusively added lol --- src/core/contracts/module-manager.ts | 7 +++++++ src/handlers/dispatchers.ts | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/contracts/module-manager.ts b/src/core/contracts/module-manager.ts index dca3dad..5dbeaee 100644 --- a/src/core/contracts/module-manager.ts +++ b/src/core/contracts/module-manager.ts @@ -19,7 +19,14 @@ export interface ModuleManager extends MetadataAccess { get(id: string): Module | undefined; set(id: string, path: Module): void; + /** + * @deprecated + */ getPublishableCommands(): CommandModule[]; + + /* + * @deprecated + */ getByNameCommandType( name: string, commandType: T, diff --git a/src/handlers/dispatchers.ts b/src/handlers/dispatchers.ts index 1c93b3c..8e4eb10 100644 --- a/src/handlers/dispatchers.ts +++ b/src/handlers/dispatchers.ts @@ -84,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] }; } } From 48f9f6ec16e650d574bd24dcbb0ed176933bfe17 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:35:53 -0600 Subject: [PATCH 05/10] fix: rm deprecated class modules, clean up, rm indirection (#355) * refactor: rm deprecations, clean up, rm indirection * fix: singleton init not being fired when inserting function * refactor and generic internal add * deprecate a few things that i impusively added lol --- src/core/_internal.ts | 4 +- src/core/contracts/module-manager.ts | 15 +++-- src/core/contracts/module-store.ts | 2 +- src/core/functions.ts | 6 +- src/core/id.ts | 13 +--- src/core/ioc/base.ts | 62 +++++++++++++------ src/core/ioc/container.ts | 9 +-- src/core/modules.ts | 58 +---------------- src/core/structures/module-store.ts | 2 +- .../structures/services/module-manager.ts | 17 +++-- src/handlers/dispatchers.ts | 10 +-- src/handlers/event-utils.ts | 58 ++++++++--------- src/handlers/ready-event.ts | 34 +++++----- src/handlers/user-defined-events.ts | 2 +- src/index.ts | 6 +- src/sern.ts | 3 +- test/core/ioc.test.ts | 12 ++++ 17 files changed, 137 insertions(+), 176 deletions(-) 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..3fd8c39 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -11,7 +11,27 @@ import type { Logging } from '../contracts/logging'; 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 @@ -29,19 +49,24 @@ 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 Insertable = + | ((container: CoreContainer) => 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: CoreContainer) => ({ [key]: v(cntr)} ))) + .expect("Failed to add " + key); + } }, /** * Exclude any dependencies from being added. @@ -57,8 +82,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: CoreContainer) => ({ [key]: v(cntr)}))) + .expect("Failed to update " + key); + } }, /** * @param key the key of the dependency @@ -82,11 +113,6 @@ type ValidDependencyConfig = | CallbackBuilder | 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 +127,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 DefaultServices.DefaultLogging); } //Build the container based on the callback provided by the user conf.build(container as CoreContainer>); @@ -119,13 +145,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 DefaultServices.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(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/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/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..643ab35 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 }; }, }), ); 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..3dd4989 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), 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..e88bd06 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); 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() + }) + }); From f8b69ae5424cf6902f733e7599318f95239a5016 Mon Sep 17 00:00:00 2001 From: jacob Date: Sat, 17 Feb 2024 11:38:37 -0600 Subject: [PATCH 06/10] stuff --- src/core/ioc/base.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index 3fd8c39..d55538d 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -3,7 +3,7 @@ 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'; @@ -127,7 +127,7 @@ function composeRoot( //container should have no client or logger yet. const hasLogger = conf.exclude?.has('@sern/logger'); if (!hasLogger) { - __add_container('@sern/logger', new DefaultServices.DefaultLogging); + __add_container('@sern/logger', new __Services.DefaultLogging); } //Build the container based on the callback provided by the user conf.build(container as CoreContainer>); @@ -151,7 +151,7 @@ export async function makeDependencies && !containerSubject.hasKey('@sern/logger'); if(includeLogger) { - __add_container('@sern/logger', new DefaultServices.DefaultLogging); + __add_container('@sern/logger', new __Services.DefaultLogging); } containerSubject.ready(); From ce8c4bf6492b9680fb1c1a530d3e0028f214ad2f Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 18 Feb 2024 09:34:53 -0600 Subject: [PATCH 07/10] fix: typings and cleanup (#356) * fix typings and cleanup * import type * rm unused import --- src/core/ioc/base.ts | 16 +++++++++------- src/core/module-loading.ts | 7 ++----- src/core/presences.ts | 6 ++---- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index d55538d..5994783 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -6,7 +6,7 @@ import { Result } from 'ts-results-es'; 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>; @@ -34,7 +34,7 @@ export function __add_container(key: string,v : Insertable) { /** * 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( @@ -49,8 +49,11 @@ export function disposeAll(logger: Logging|undefined) { ?.disposeAll() .then(() => logger?.info({ message: 'Cleaning container and crashing' })); } +type UnpackedDependencies = { + [K in keyof Dependencies]: UnpackFunction +} type Insertable = - | ((container: CoreContainer) => unknown) + | ((container: UnpackedDependencies) => unknown) | object const dependencyBuilder = (container: any, excluded: string[] ) => { return { @@ -64,7 +67,7 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { .expect("Failed to add " + key); } else { Result.wrap(() => - container.add((cntr: CoreContainer) => ({ [key]: v(cntr)} ))) + container.add((cntr: UnpackedDependencies) => ({ [key]: v(cntr)} ))) .expect("Failed to add " + key); } }, @@ -87,7 +90,7 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { .expect("Failed to update " + key); } else { Result.wrap(() => - container.upsert((cntr: CoreContainer) => ({ [key]: v(cntr)}))) + container.upsert((cntr: UnpackedDependencies) => ({ [key]: v(cntr)}))) .expect("Failed to update " + key); } }, @@ -107,10 +110,9 @@ const dependencyBuilder = (container: any, excluded: string[] ) => { }; }; -type CallbackBuilder = (c: ReturnType) => any type ValidDependencyConfig = - | CallbackBuilder + | ((c: ReturnType) => any) | DependencyConfiguration; diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index a5e8969..0833672 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -1,4 +1,3 @@ -import { Result } from 'ts-results-es'; import { type Observable, from, mergeMap, ObservableInput } from 'rxjs'; import { readdir, stat } from 'fs/promises'; import { basename, extname, join, resolve, parse, dirname } from 'path'; @@ -42,9 +41,7 @@ export async function importModule(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/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; } /** From 210652281242fbeb959dddc1e39b15d7e15f29e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:35:24 -0600 Subject: [PATCH 08/10] chore(main): release 3.3.3 (#357) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e390456..4ecae34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [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..1620f6f 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.3", "description": "A complete, customizable, typesafe, & reactive framework for discord bots.", "main": "./dist/index.js", "module": "./dist/index.mjs", From 90e55dfa1466c91e5da48922251309331921b1ef Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 18 Mar 2024 01:47:14 -0500 Subject: [PATCH 09/10] fix: sern emitter err (#358) * prep for fix * fix ? (not tested * fix error event not emitting payload --- src/core/operators.ts | 11 ++++++----- src/core/structures/services/error-handling.ts | 2 +- src/handlers/event-utils.ts | 4 ++-- src/handlers/user-defined-events.ts | 2 +- src/sern.ts | 2 +- src/types/utility.ts | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) 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/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/handlers/event-utils.ts b/src/handlers/event-utils.ts index 643ab35..fc7a16c 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -246,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/user-defined-events.ts b/src/handlers/user-defined-events.ts index 3dd4989..66e7dfb 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -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/sern.ts b/src/sern.ts index e88bd06..fd458f4 100644 --- a/src/sern.ts +++ b/src/sern.ts @@ -58,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; From a19edaf8838dcf088d3947f4a6aa6213d8f5bb9e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 01:57:13 -0500 Subject: [PATCH 10/10] chore(main): release 3.3.4 (#359) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ecae34..01b34c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # 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) diff --git a/package.json b/package.json index 1620f6f..a792359 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@sern/handler", "packageManager": "yarn@3.5.0", - "version": "3.3.3", + "version": "3.3.4", "description": "A complete, customizable, typesafe, & reactive framework for discord bots.", "main": "./dist/index.js", "module": "./dist/index.mjs",