From 45cbda7b42f3a73e952f2ac2e73e0fbd1f14587c Mon Sep 17 00:00:00 2001 From: SrIzan10 <66965250+SrIzan10@users.noreply.github.com> Date: Sat, 10 Feb 2024 00:46:16 +0100 Subject: [PATCH] refactor: cleanup (#348) * some wip code Co-authored-by: Jacob Nguyen * general idea * style * making shrimple truly optional * got optional localizer working * proposing api notation? * prepare for localization map * add localsFor * merge some internals * boss call * add test for init functionality * add documentation * inline and cleanup * feat: logging for experimental json loading * loosen typings * dev workflow and cleaning up comments * cleaning up a bit more * rename Localizer -> Localization * more documentation, change dir for default localizer * some tests * " * move stuff, refactor, deprecate * yarnb * Update index.ts --------- Co-authored-by: Jacob Nguyen Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Co-authored-by: jacob --- .github/workflows/npm-publish-dev.yml | 34 ++++++++++ package.json | 4 +- src/core/_internal.ts | 2 +- src/core/contracts/disposable.ts | 9 --- src/core/contracts/emitter.ts | 2 + src/core/contracts/error-handling.ts | 2 - src/core/contracts/hooks.ts | 16 +++++ src/core/contracts/index.ts | 3 +- src/core/contracts/init.ts | 9 --- src/core/functions.ts | 46 +++++++++++++- src/core/ioc/base.ts | 63 +++++++++++++------ src/core/ioc/container.ts | 26 +++++--- src/core/ioc/dependency-injection.ts | 36 +++-------- src/core/ioc/hooks.ts | 3 +- src/core/module-loading.ts | 14 +++-- src/core/operators.ts | 3 +- src/core/predicates.ts | 34 ---------- src/core/structures/context.ts | 2 +- src/core/structures/index.ts | 2 +- src/core/structures/module-store.ts | 5 +- src/core/structures/sern-emitter.ts | 89 --------------------------- src/handlers/dispatchers.ts | 5 +- src/handlers/event-utils.ts | 18 +++--- src/handlers/interaction-event.ts | 7 ++- src/handlers/message-event.ts | 9 +-- src/handlers/presence.ts | 3 +- src/handlers/ready-event.ts | 2 +- src/sern.ts | 27 +++----- src/types/core-modules.ts | 1 - src/types/core-plugin.ts | 6 +- src/types/ioc.ts | 13 +++- src/types/utility.ts | 1 - test/core/id.test.ts | 64 +++++++++++++++++++ test/core/ioc.test.ts | 17 ++++- test/core/services.test.ts | 3 + yarn.lock | 3 +- 36 files changed, 311 insertions(+), 272 deletions(-) create mode 100644 .github/workflows/npm-publish-dev.yml delete mode 100644 src/core/contracts/disposable.ts create mode 100644 src/core/contracts/hooks.ts delete mode 100644 src/core/contracts/init.ts delete mode 100644 src/core/predicates.ts delete mode 100644 src/core/structures/sern-emitter.ts create mode 100644 test/core/id.test.ts diff --git a/.github/workflows/npm-publish-dev.yml b/.github/workflows/npm-publish-dev.yml new file mode 100644 index 00000000..27c1b03f --- /dev/null +++ b/.github/workflows/npm-publish-dev.yml @@ -0,0 +1,34 @@ +name: Continuous Delivery + +on: + push: + branches: + - main + paths: + - 'src/**' + - 'package.json' + +jobs: + Publish: + name: Publishing Dev + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + + - name: Install Node.js dependencies + run: npm i && npm run build:dev + + - name: Publish to npm + run: | + npm version premajor --preid "dev.$(git rev-parse --verify --short HEAD)" --git-tag-version=false + npm publish --tag dev + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/package.json b/package.json index 3ba82869..26f45aea 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "@typescript-eslint/eslint-plugin": "5.58.0", "@typescript-eslint/parser": "5.59.1", "discord.js": "^14.11.0", - "esbuild": "^0.17.0", "eslint": "8.39.0", "prettier": "2.8.8", "tsup": "^6.7.0", @@ -95,5 +94,8 @@ "type": "git", "url": "git+https://github.com/sern-handler/handler.git" }, + "engines": { + "node": ">= 18.16.x" + }, "homepage": "https://sern.dev" } diff --git a/src/core/_internal.ts b/src/core/_internal.ts index 026d036f..513abaa2 100644 --- a/src/core/_internal.ts +++ b/src/core/_internal.ts @@ -1,6 +1,5 @@ export * as Id from './id'; export * from './operators'; -export * from './predicates'; export * as Files from './module-loading'; export * from './functions'; export type { VoidResult } from '../types/core-plugin'; @@ -8,3 +7,4 @@ export { SernError } from './structures/enums'; export { ModuleStore } from './structures/module-store'; export * as DefaultServices from './structures/services'; export { useContainerRaw } from './ioc/base' + diff --git a/src/core/contracts/disposable.ts b/src/core/contracts/disposable.ts deleted file mode 100644 index 42b0142c..00000000 --- a/src/core/contracts/disposable.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Awaitable } from '../../types/utility'; - -/** - * Represents a Disposable contract. - * Let dependencies implement this to dispose and cleanup. - */ -export interface Disposable { - dispose(): Awaitable; -} diff --git a/src/core/contracts/emitter.ts b/src/core/contracts/emitter.ts index abb3a29c..11bbc286 100644 --- a/src/core/contracts/emitter.ts +++ b/src/core/contracts/emitter.ts @@ -1,3 +1,5 @@ +//i deleted it, hmm so how should we allow users to enable localization? +// a import type { AnyFunction } from '../../types/utility'; export interface Emitter { diff --git a/src/core/contracts/error-handling.ts b/src/core/contracts/error-handling.ts index 7d17a563..d5e94550 100644 --- a/src/core/contracts/error-handling.ts +++ b/src/core/contracts/error-handling.ts @@ -1,5 +1,3 @@ -import type { CommandModule,Processed, EventModule } from "../../types/core-modules"; - /** * @since 2.0.0 */ diff --git a/src/core/contracts/hooks.ts b/src/core/contracts/hooks.ts new file mode 100644 index 00000000..ee329dde --- /dev/null +++ b/src/core/contracts/hooks.ts @@ -0,0 +1,16 @@ + +/** + * Represents an initialization contract. + * Let dependencies implement this to initiate some logic. + */ +export interface Init { + init(): unknown; +} + +/** + * Represents a Disposable contract. + * Let dependencies implement this to dispose and cleanup. + */ +export interface Disposable { + dispose(): unknown; +} diff --git a/src/core/contracts/index.ts b/src/core/contracts/index.ts index 7d123b1e..f0eb130a 100644 --- a/src/core/contracts/index.ts +++ b/src/core/contracts/index.ts @@ -2,6 +2,5 @@ export * from './error-handling'; export * from './logging'; export * from './module-manager'; export * from './module-store'; -export * from './init'; +export * from './hooks'; export * from './emitter'; -export * from './disposable' diff --git a/src/core/contracts/init.ts b/src/core/contracts/init.ts deleted file mode 100644 index eec1e4ce..00000000 --- a/src/core/contracts/init.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Awaitable } from '../../types/utility'; - -/** - * Represents an initialization contract. - * Let dependencies implement this to initiate some logic. - */ -export interface Init { - init(): Awaitable; -} diff --git a/src/core/functions.ts b/src/core/functions.ts index f45bf8a8..60d62e9b 100644 --- a/src/core/functions.ts +++ b/src/core/functions.ts @@ -1,9 +1,19 @@ import { Err, Ok } from 'ts-results-es'; -import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; -import type { SernAutocompleteData, SernOptionsData } from '../types/core-modules'; +import type { Module, SernAutocompleteData, SernOptionsData } from '../types/core-modules'; import type { AnyCommandPlugin, AnyEventPlugin, Plugin } from '../types/core-plugin'; -import { PluginType } from './structures'; +import type { + AnySelectMenuInteraction, + ButtonInteraction, + ChatInputCommandInteraction, + MessageContextMenuCommandInteraction, + ModalSubmitInteraction, + UserContextMenuCommandInteraction, + AutocompleteInteraction +} from 'discord.js'; +import { ApplicationCommandOptionType, InteractionType } from 'discord.js' +import { PayloadType, PluginType } from './structures'; import assert from 'assert'; +import { Payload } from '../types/utility'; //function wrappers for empty ok / err export const ok = /* @__PURE__*/ () => Ok.EMPTY; @@ -81,3 +91,33 @@ export function treeSearch( } } } + + +interface InteractionTypable { + type: InteractionType; +} +//discord.js pls fix ur typings or i will >:( +type AnyMessageComponentInteraction = AnySelectMenuInteraction | ButtonInteraction; +type AnyCommandInteraction = + | ChatInputCommandInteraction + | MessageContextMenuCommandInteraction + | UserContextMenuCommandInteraction; + +export function isMessageComponent(i: InteractionTypable): i is AnyMessageComponentInteraction { + return i.type === InteractionType.MessageComponent; +} +export function isCommand(i: InteractionTypable): i is AnyCommandInteraction { + return i.type === InteractionType.ApplicationCommand; +} +export function isAutocomplete(i: InteractionTypable): i is AutocompleteInteraction { + return i.type === InteractionType.ApplicationCommandAutocomplete; +} + +export function isModal(i: InteractionTypable): i is ModalSubmitInteraction { + return i.type === InteractionType.ModalSubmit; +} + +export function resultPayload +(type: T, module?: Module, reason?: unknown) { + return { type, module, reason } as Payload & { type : T }; +} diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index 9de0c75f..540204aa 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -1,11 +1,12 @@ import * as assert from 'assert'; -import { composeRoot, useContainer } from './dependency-injection'; -import type { DependencyConfiguration } from '../../types/ioc'; +import { useContainer } from './dependency-injection'; +import type { CoreDependencies, DependencyConfiguration } from '../../types/ioc'; import { CoreContainer } from './container'; -import { Result } from 'ts-results-es' +import { Result } from 'ts-results-es'; import { DefaultServices } from '../_internal'; import { AnyFunction } from '../../types/utility'; import type { Logging } from '../contracts/logging'; + //SIDE EFFECT: GLOBAL DI let containerSubject: CoreContainer>; @@ -29,19 +30,18 @@ export function disposeAll(logger: Logging|undefined) { .then(() => logger?.info({ message: 'Cleaning container and crashing' })); } -const dependencyBuilder = (container: any, excluded: string[]) => { +const dependencyBuilder = (container: any, excluded: string[] ) => { type Insertable = | ((container: CoreContainer) => unknown ) - | Record + | object 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); + Result.wrap(() => container.add({ [key]: v})) + .expect("Failed to add " + key); }, /** * Exclude any dependencies from being added. @@ -50,15 +50,15 @@ const dependencyBuilder = (container: any, excluded: string[]) => { exclude(...keys: (keyof Dependencies)[]) { keys.forEach(key => excluded.push(key)); }, + /** * @param key the key of the dependency * @param v The dependency to swap out. * Swap out a preexisting dependency. */ swap(key: keyof Dependencies, v: Insertable) { - Result - .wrap(() => container.upsert({ [key]: v })) - .expect("Failed to update " + key); + Result.wrap(() => container.upsert({ [key]: v })) + .expect("Failed to update " + key); }, /** * @param key the key of the dependency @@ -70,9 +70,8 @@ const dependencyBuilder = (container: any, excluded: string[]) => { * Swap out a preexisting dependency. */ addDisposer(key: keyof Dependencies, cleanup: AnyFunction) { - Result - .wrap(() => container.addDisposer({ [key] : cleanup })) - .expect("Failed to addDisposer for" + key); + Result.wrap(() => container.addDisposer({ [key] : cleanup })) + .expect("Failed to addDisposer for" + key); } }; }; @@ -87,15 +86,45 @@ export const insertLogger = (containerSubject: CoreContainer) => { containerSubject .upsert({'@sern/logger': () => new DefaultServices.DefaultLogging}); } + + +/** + * Given the user's conf, check for any excluded/included dependency keys. + * Then, call conf.build to get the rest of the users' dependencies. + * Finally, update the containerSubject with the new container state + * @param conf + */ +function composeRoot( + container: CoreContainer>, + conf: DependencyConfiguration, +) { + //container should have no client or logger yet. + const hasLogger = conf.exclude?.has('@sern/logger'); + if (!hasLogger) { + insertLogger(container); + } + //Build the container based on the callback provided by the user + conf.build(container as CoreContainer>); + + if (!hasLogger) { + container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); + } + + container.ready(); +} + export async function makeDependencies (conf: ValidDependencyConfig) { containerSubject = new CoreContainer(); if(typeof conf === 'function') { const excluded: string[] = []; conf(dependencyBuilder(containerSubject, excluded)); + + const includeLogger = + !excluded.includes('@sern/logger') + && !containerSubject.getTokens()['@sern/logger']; - if(!excluded.includes('@sern/logger') - && !containerSubject.getTokens()['@sern/logger']) { + if(includeLogger) { insertLogger(containerSubject); } @@ -107,5 +136,3 @@ export async function makeDependencies return useContainer(); } - - diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts index ff61173c..638eb8f4 100644 --- a/src/core/ioc/container.ts +++ b/src/core/ioc/container.ts @@ -1,9 +1,10 @@ import { Container } from 'iti'; -import { Disposable, SernEmitter } from '../'; +import { Disposable } from '../'; import * as assert from 'node:assert'; import { Subject } from 'rxjs'; import { DefaultServices, ModuleStore } from '../_internal'; -import * as Hooks from './hooks' +import * as Hooks from './hooks'; +import { EventEmitter } from 'node:events'; /** @@ -17,12 +18,13 @@ export class CoreContainer> extends Container) - .add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling(), - '@sern/emitter': () => new SernEmitter, + .add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling, + '@sern/emitter': () => new EventEmitter({ captureRejections: true }), '@sern/store': () => new ModuleStore }) .add(ctx => { return { '@sern/modules': () => @@ -33,19 +35,25 @@ export class CoreContainer> extends Container)._context[key]); + } override async disposeAll() { - const otherDisposables = Object .entries(this._context) .flatMap(([key, value]) => 'dispose' in value ? [key] : []); - - for(const key of otherDisposables) { + otherDisposables.forEach(key => { + //possible source of bug: dispose is a property. this.addDisposer({ [key]: (dep: Disposable) => dep.dispose() } as never); - } - await super.disposeAll() + }) + await super.disposeAll(); } + + + ready() { this.ready$.complete(); this.ready$.unsubscribe(); diff --git a/src/core/ioc/dependency-injection.ts b/src/core/ioc/dependency-injection.ts index 126efe3b..f792b8ae 100644 --- a/src/core/ioc/dependency-injection.ts +++ b/src/core/ioc/dependency-injection.ts @@ -1,6 +1,6 @@ -import type { CoreDependencies, DependencyConfiguration, IntoDependencies } from '../../types/ioc'; -import { insertLogger, useContainerRaw } from './base'; -import { CoreContainer } from './container'; +import assert from 'node:assert'; +import type { IntoDependencies } from '../../types/ioc'; +import { useContainerRaw } from './base'; /** * @__PURE__ @@ -25,6 +25,7 @@ export function transient(cb: () => () => T) { * The new Service api, a cleaner alternative to useContainer * To obtain intellisense, ensure a .d.ts file exists in the root of compilation. * Usually our scaffolding tool takes care of this. + * Note: this method only works AFTER your container has been initiated * @since 3.0.0 * @example * ```ts @@ -34,7 +35,9 @@ export function transient(cb: () => () => T) { * */ export function Service(key: T) { - return useContainerRaw().get(key)!; + const dep = useContainerRaw().get(key)!; + assert(dep, "Requested key " + key + " returned undefined"); + return dep; } /** * @since 3.0.0 @@ -46,32 +49,9 @@ export function Services(...keys: [...T] return keys.map(k => container.get(k)!) as IntoDependencies; } -/** - * Given the user's conf, check for any excluded dependency keys. - * Then, call conf.build to get the rest of the users' dependencies. - * Finally, update the containerSubject with the new container state - * @param conf - */ -export function composeRoot( - container: CoreContainer>, - conf: DependencyConfiguration, -) { - //container should have no client or logger yet. - const hasLogger = conf.exclude?.has('@sern/logger'); - if (!hasLogger) { - insertLogger(container); - } - //Build the container based on the callback provided by the user - conf.build(container as CoreContainer>); - - if (!hasLogger) { - container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); - } - - container.ready(); -} export function useContainer() { return (...keys: [...V]) => keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as IntoDependencies; } + diff --git a/src/core/ioc/hooks.ts b/src/core/ioc/hooks.ts index a1db322e..0a325572 100644 --- a/src/core/ioc/hooks.ts +++ b/src/core/ioc/hooks.ts @@ -9,7 +9,7 @@ type HookName = 'init'; export const createInitListener = (coreContainer : CoreContainer) => { const initCalled = new Set(); const hasCallableMethod = createPredicate(initCalled); - const unsubscribe = coreContainer.on('containerUpserted', async (event) => { + const unsubscribe = coreContainer.on('containerUpserted', async event => { if(isNotHookable(event)) { return; @@ -21,6 +21,7 @@ export const createInitListener = (coreContainer : CoreContainer) => { } }); + return { unsubscribe }; } diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index 7bf406c9..a5e89690 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -7,6 +7,7 @@ import { createRequire } from 'node:module'; import type { ImportPayload, Wrapper } from '../types/core'; import type { Module } from '../types/core-modules'; import { existsSync } from 'fs'; +import type { Logging } from './contracts/logging'; export const shouldHandle = (path: string, fpath: string) => { const file_name = fpath+extname(path); @@ -18,6 +19,7 @@ export const shouldHandle = (path: string, fpath: string) => { export type ModuleResult = Promise>; + /** * Import any module based on the absolute path. * This can accept four types of exported modules @@ -104,13 +106,13 @@ async function* readPaths(dir: string): AsyncGenerator { } } -const requir = createRequire(import.meta.url); +export const requir = createRequire(import.meta.url); -export function loadConfig(wrapper: Wrapper | 'file'): Wrapper { +export function loadConfig(wrapper: Wrapper | 'file', log: Logging | undefined): Wrapper { if (wrapper !== 'file') { return wrapper; } - console.log('Experimental loading of sern.config.json'); + log?.info({ message: 'Experimental loading of sern.config.json'}); const config = requir(resolve('sern.config.json')); const makePath = (dir: PropertyKey) => @@ -118,14 +120,14 @@ export function loadConfig(wrapper: Wrapper | 'file'): Wrapper { ? join('dist', config.paths[dir]!) : join(config.paths[dir]!); - console.log('Loading config: ', config); + log?.info({ message: 'Loading config: ' + JSON.stringify(config, null, 4) }); const commandsPath = makePath('commands'); - console.log('Commands path is set to', commandsPath); + log?.info({ message: `Commands path is set to ${commandsPath}` }); let eventsPath: string | undefined; if (config.paths.events) { eventsPath = makePath('events'); - console.log('Events path is set to', eventsPath); + log?.info({ message: `Events path is set to ${eventsPath} `}); } return { defaultPrefix: config.defaultPrefix, diff --git a/src/core/operators.ts b/src/core/operators.ts index 12743ab9..d534c5b1 100644 --- a/src/core/operators.ts +++ b/src/core/operators.ts @@ -71,8 +71,7 @@ export function handleError(crashHandler: ErrorHandling, logging?: Logging) { } // Temporary until i get rxjs operators working on ts-results-es export const filterTap = (onErr: (e: R) => void): OperatorFunction, K> => - pipe( - concatMap(result => { + pipe(concatMap(result => { if(result.isOk()) { return of(result.value) } diff --git a/src/core/predicates.ts b/src/core/predicates.ts deleted file mode 100644 index a325e7a3..00000000 --- a/src/core/predicates.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { - AnySelectMenuInteraction, - AutocompleteInteraction, - ButtonInteraction, - ChatInputCommandInteraction, - MessageContextMenuCommandInteraction, - ModalSubmitInteraction, - UserContextMenuCommandInteraction, -} from 'discord.js'; -import { InteractionType } from 'discord.js'; - -interface InteractionTypable { - type: InteractionType; -} -//discord.js pls fix ur typings or i will >:( -type AnyMessageComponentInteraction = AnySelectMenuInteraction | ButtonInteraction; -type AnyCommandInteraction = - | ChatInputCommandInteraction - | MessageContextMenuCommandInteraction - | UserContextMenuCommandInteraction; - -export function isMessageComponent(i: InteractionTypable): i is AnyMessageComponentInteraction { - return i.type === InteractionType.MessageComponent; -} -export function isCommand(i: InteractionTypable): i is AnyCommandInteraction { - return i.type === InteractionType.ApplicationCommand; -} -export function isAutocomplete(i: InteractionTypable): i is AutocompleteInteraction { - return i.type === InteractionType.ApplicationCommandAutocomplete; -} - -export function isModal(i: InteractionTypable): i is ModalSubmitInteraction { - return i.type === InteractionType.ModalSubmit; -} diff --git a/src/core/structures/context.ts b/src/core/structures/context.ts index 6ceb7abc..fb37c05c 100644 --- a/src/core/structures/context.ts +++ b/src/core/structures/context.ts @@ -1,4 +1,4 @@ -import { +import type { BaseInteraction, ChatInputCommandInteraction, Client, diff --git a/src/core/structures/index.ts b/src/core/structures/index.ts index e3c08dcb..8eeb6f92 100644 --- a/src/core/structures/index.ts +++ b/src/core/structures/index.ts @@ -1,5 +1,5 @@ export { CommandType, PluginType, PayloadType, EventType } from './enums'; export * from './context'; -export * from './sern-emitter'; export * from './services'; export * from './module-store'; + diff --git a/src/core/structures/module-store.ts b/src/core/structures/module-store.ts index 6e754e1a..44d9ca1c 100644 --- a/src/core/structures/module-store.ts +++ b/src/core/structures/module-store.ts @@ -1,12 +1,11 @@ import { CommandMeta, Module } from '../../types/core-modules'; -import { CoreModuleStore } from '../contracts'; /* - * @internal + * @deprecated * Version 4.0.0 will internalize this api. Please refrain from using ModuleStore! * For interacting with modules, use the ModuleManager instead. */ -export class ModuleStore implements CoreModuleStore { +export class ModuleStore { metadata = new WeakMap(); commands = new Map(); } diff --git a/src/core/structures/sern-emitter.ts b/src/core/structures/sern-emitter.ts deleted file mode 100644 index 0b4f188d..00000000 --- a/src/core/structures/sern-emitter.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { EventEmitter } from 'node:events'; -import { PayloadType } from '../../core/structures'; -import { Module } from '../../types/core-modules'; -import { SernEventsMapping, Payload } from '../../types/utility'; - -/** - * @since 1.0.0 - */ -export class SernEmitter extends EventEmitter { - constructor() { - super({ captureRejections: true }); - } - /** - * Listening to sern events with on. This event stays on until a crash or a normal exit - * @param eventName - * @param listener what to do with the data - */ - public override on( - eventName: T, - listener: (...args: SernEventsMapping[T][]) => void, - ): this { - return super.on(eventName, listener); - } - /** - * Listening to sern events with on. This event stays on until a crash or a normal exit - * @param eventName - * @param listener what to do with the data - */ - public override once( - eventName: T, - listener: (...args: SernEventsMapping[T][]) => void, - ): this { - return super.once(eventName, listener); - } - /** - * Listening to sern events with on. This event stays on until a crash or a normal exit - * @param eventName - * @param args the arguments for emitting the eventName - */ - public override emit( - eventName: T, - ...args: SernEventsMapping[T] - ): boolean { - return super.emit(eventName, ...args); - } - private static payload( - type: PayloadType, - module?: Module, - reason?: unknown, - ) { - return { type, module, reason } as T; - } - - /** - * Creates a compliant SernEmitter failure payload - * @param module - * @param reason - */ - static failure(module?: Module, reason?: unknown) { - //The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload - // same goes to the other methods below - return SernEmitter.payload( - PayloadType.Failure, - module, - reason, - ); - } - /** - * Creates a compliant SernEmitter module success payload - * @param module - */ - static success(module: Module) { - return SernEmitter.payload( - PayloadType.Success, - module, - ); - } - /** - * Creates a compliant SernEmitter module warning payload - * @param reason - */ - static warning(reason: unknown) { - return SernEmitter.payload( - PayloadType.Warning, - undefined, - reason, - ); - } -} diff --git a/src/handlers/dispatchers.ts b/src/handlers/dispatchers.ts index 5162e1ee..d52f7bee 100644 --- a/src/handlers/dispatchers.ts +++ b/src/handlers/dispatchers.ts @@ -85,10 +85,7 @@ export function createDispatcher(payload: { args: [payload.event], }; } - return { - module: payload.module, - args: contextArgs(payload.event), - }; + return { module: payload.module, args: contextArgs(payload.event) }; } default: return { module: payload.module, diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index 6182aee3..a2e757b1 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -21,11 +21,11 @@ import { handleError, SernError, VoidResult, + resultPayload, } from '../core/_internal'; -import { Emitter, ErrorHandling, Logging, ModuleManager } from '../core'; +import { Emitter, ErrorHandling, Logging, ModuleManager, PayloadType } from '../core'; import { contextArgs, createDispatcher } from './dispatchers'; import { ObservableInput, pipe } from 'rxjs'; -import { SernEmitter } from '../core'; import { Err, Ok, Result } from 'ts-results-es'; import type { Awaitable } from '../types/utility'; import type { ControlPlugin } from '../types/core-plugin'; @@ -86,8 +86,7 @@ export function createInteractionHandler( module: payload.module, event, }))); - }, -); + }); } export function createMessageHandler( @@ -168,10 +167,10 @@ export function executeModule( concatMap(() => Result.wrapAsync(async () => task())), concatMap(result => { if (result.isOk()) { - emitter.emit('module.activate', SernEmitter.success(module)); + emitter.emit('module.activate', resultPayload(PayloadType.Success, module)); return EMPTY; } - return throwError(() => SernEmitter.failure(module, result.error)); + return throwError(() => resultPayload(PayloadType.Failure, module, result.error)); }), ); @@ -218,13 +217,10 @@ export function callInitPlugins>(sernEmitter: Emi createResultResolver({ createStream: args => from(args.module.plugins).pipe(callPlugin(args)), onStop: (module: T) => { - sernEmitter.emit( - 'module.register', - SernEmitter.failure(module, SernError.PluginFailure), - ); + sernEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); }, onNext: ({ module }) => { - sernEmitter.emit('module.register', SernEmitter.success(module)); + sernEmitter.emit('module.register', resultPayload(PayloadType.Success, module)); return { module }; }, }), diff --git a/src/handlers/interaction-event.ts b/src/handlers/interaction-event.ts index f9b90e14..06523417 100644 --- a/src/handlers/interaction-event.ts +++ b/src/handlers/interaction-event.ts @@ -1,6 +1,6 @@ import { Interaction } from 'discord.js'; import { mergeMap, merge } from 'rxjs'; -import { SernEmitter } from '../core'; +import { PayloadType } from '../core'; import { isAutocomplete, isCommand, @@ -9,6 +9,7 @@ import { sharedEventStream, SernError, filterTap, + resultPayload, } from '../core/_internal'; import { createInteractionHandler, executeModule, makeModuleExecutor } from './_internal'; import type { DependencyList } from '../types/ioc'; @@ -25,8 +26,8 @@ export function interactionHandler([emitter, err, log, modules, client]: Depende ); return interactionHandler$ .pipe( - filterTap(e => emitter.emit('warning', SernEmitter.warning(e))), + filterTap(e => emitter.emit('warning', resultPayload(PayloadType.Warning, undefined, e))), makeModuleExecutor(module => - emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure))), + emitter.emit('module.activate', resultPayload(PayloadType.Failure, module, SernError.PluginFailure))), mergeMap(payload => executeModule(emitter, log, err, payload))); } diff --git a/src/handlers/message-event.ts b/src/handlers/message-event.ts index d234ca70..86b14466 100644 --- a/src/handlers/message-event.ts +++ b/src/handlers/message-event.ts @@ -1,7 +1,7 @@ import { mergeMap, EMPTY } from 'rxjs'; import type { Message } from 'discord.js'; -import { SernEmitter } from '../core'; -import { sharedEventStream, SernError, filterTap } from '../core/_internal'; +import { PayloadType } from '../core'; +import { sharedEventStream, SernError, filterTap, resultPayload } from '../core/_internal'; import { createMessageHandler, executeModule, makeModuleExecutor } from './_internal'; import type { DependencyList } from '../types/ioc'; @@ -38,9 +38,10 @@ export function messageHandler( const msgCommands$ = handle(isNonBot(defaultPrefix)); return msgCommands$.pipe( - filterTap((e) => emitter.emit('warning', SernEmitter.warning(e))), + filterTap((e) => emitter.emit('warning', resultPayload(PayloadType.Warning, undefined, e))), makeModuleExecutor(module => { - emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + const result = resultPayload(PayloadType.Failure, module, SernError.PluginFailure); + emitter.emit('module.activate', result); }), mergeMap(payload => executeModule(emitter, log, err, payload))); } diff --git a/src/handlers/presence.ts b/src/handlers/presence.ts index 38afa6e0..761fbb12 100644 --- a/src/handlers/presence.ts +++ b/src/handlers/presence.ts @@ -19,7 +19,6 @@ const parseConfig = async (conf: Promise) => { .pipe(scan(onRepeat, s), startWith(s)); } - //take 1? return of(s).pipe(take(1)); }) }; @@ -37,7 +36,7 @@ export const presenceHandler = (path: string, setPresence: SetPresence) => { }) const module$ = from(presence); return module$.pipe( - //compose:. + //compose: //call the execute function, passing that result into parseConfig. //concatMap resolves the promise, and passes it to the next concatMap. concatMap(fn => parseConfig(fn())), diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index b4e811bd..80c6ad03 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -9,7 +9,7 @@ import * as util from 'node:util'; import type { DependencyList } from '../types/ioc'; import type { AnyModule, Processed } from '../types/core-modules'; -export function startReadyEvent( +export function readyHandler( [sEmitter, , , moduleManager, client]: DependencyList, allPaths: ObservableInput, ) { diff --git a/src/sern.ts b/src/sern.ts index 495e331c..e633be01 100644 --- a/src/sern.ts +++ b/src/sern.ts @@ -1,11 +1,11 @@ import { handleCrash } from './handlers/_internal'; import callsites from 'callsites'; -import { err, ok, Files } from './core/_internal'; +import { Files } from './core/_internal'; import { merge } from 'rxjs'; import { Services } from './core/ioc'; import { Wrapper } from './types/core'; import { eventsHandler } from './handlers/user-defined-events'; -import { startReadyEvent } from './handlers/ready-event'; +import { readyHandler } from './handlers/ready-event'; import { messageHandler } from './handlers/message-event'; import { interactionHandler } from './handlers/interaction-event'; import { presenceHandler } from './handlers/presence'; @@ -23,14 +23,17 @@ import { Client } from 'discord.js'; * }) * ``` */ - export function init(maybeWrapper: Wrapper | 'file') { const startTime = performance.now(); - const wrapper = Files.loadConfig(maybeWrapper); - const dependencies = useDependencies(); + const dependencies = Services('@sern/emitter', + '@sern/errors', + '@sern/logger', + '@sern/modules', + '@sern/client'); const logger = dependencies[2], errorHandler = dependencies[1]; + const wrapper = Files.loadConfig(maybeWrapper, logger); if (wrapper.events !== undefined) { eventsHandler(dependencies, Files.getFullPathTree(wrapper.events)); } @@ -38,7 +41,7 @@ export function init(maybeWrapper: Wrapper | 'file') { const initCallsite = callsites()[1].getFileName(); const presencePath = Files.shouldHandle(initCallsite!, "presence"); //Ready event: load all modules and when finished, time should be taken and logged - startReadyEvent(dependencies, Files.getFullPathTree(wrapper.commands)) + readyHandler(dependencies, Files.getFullPathTree(wrapper.commands)) .add(() => { const time = ((performance.now() - startTime) / 1000).toFixed(2); dependencies[0].emit('modulesLoaded'); @@ -56,15 +59,3 @@ export function init(maybeWrapper: Wrapper | 'file') { // listening to the message stream and interaction stream merge(messages$, interactions$).pipe(handleCrash(errorHandler, logger)).subscribe(); } - -function useDependencies() { - return Services( - '@sern/emitter', - '@sern/errors', - '@sern/logger', - '@sern/modules', - '@sern/client', - ); -} - - diff --git a/src/types/core-modules.ts b/src/types/core-modules.ts index e53320a4..d0f98349 100644 --- a/src/types/core-modules.ts +++ b/src/types/core-modules.ts @@ -20,7 +20,6 @@ import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './c import { Awaitable, Args, SlashOptions, SernEventsMapping } from './utility'; - export interface CommandMeta { fullPath: string; id: string; diff --git a/src/types/core-plugin.ts b/src/types/core-plugin.ts index fb626990..8ece7c3c 100644 --- a/src/types/core-plugin.ts +++ b/src/types/core-plugin.ts @@ -33,9 +33,9 @@ import type { TextCommand, UserSelectCommand, } from './core-modules'; -import { Args, Awaitable, Payload, SlashOptions } from './utility'; -import { CommandType, Context, EventType, PluginType } from '../core'; -import { +import type { Args, Awaitable, Payload, SlashOptions } from './utility'; +import type { CommandType, Context, EventType, PluginType } from '../core'; +import type { ButtonInteraction, ChannelSelectMenuInteraction, ClientEvents, diff --git a/src/types/ioc.ts b/src/types/ioc.ts index bbfb02e5..be5278a9 100644 --- a/src/types/ioc.ts +++ b/src/types/ioc.ts @@ -26,11 +26,15 @@ export type DependencyList = [ export interface CoreDependencies { '@sern/client': () => Contracts.Emitter; - '@sern/logger'?: () => Contracts.Logging; '@sern/emitter': () => Contracts.Emitter; + /** + * @deprecated + * Will be removed and turned internal + */ '@sern/store': () => Contracts.CoreModuleStore; '@sern/modules': () => Contracts.ModuleManager; '@sern/errors': () => Contracts.ErrorHandling; + '@sern/logger'?: () => Contracts.Logging; } export type DependencyFromKey = Dependencies[T]; @@ -39,8 +43,13 @@ export type IntoDependencies = { [Index in keyof Tuple]: UnpackFunction>>; //Unpack and make NonNullable } & { length: Tuple['length'] }; +/** + * @deprecated This old signature will be incompatible with future versions of sern. + */ export interface DependencyConfiguration { - //@deprecated. Loggers will always be included in the future + /* + * @deprecated. Loggers will be opt-in the future + */ exclude?: Set<'@sern/logger'>; build: ( root: Container, {}>, diff --git a/src/types/utility.ts b/src/types/utility.ts index a2535875..65efc3d6 100644 --- a/src/types/utility.ts +++ b/src/types/utility.ts @@ -29,5 +29,4 @@ export type Payload = | { type: PayloadType.Warning; reason: string }; - export type ReplyOptions = string | Omit | MessageReplyOptions; diff --git a/test/core/id.test.ts b/test/core/id.test.ts new file mode 100644 index 00000000..9d130a01 --- /dev/null +++ b/test/core/id.test.ts @@ -0,0 +1,64 @@ +import { CommandType } from '../../src/core'; +import * as Id from '../../src/core/id' +import { expect, test } from 'vitest' + +test('id -> Text', () => { + const bothCmdId = Id.create("ping", CommandType.Text) + expect(bothCmdId).toBe("ping_T") +}) + +test('id -> Both', () => { + const bothCmdId = Id.create("ping", CommandType.Both) + expect(bothCmdId).toBe("ping_B") +}) + +test('id -> CtxMsg', () => { + const bothCmdId = Id.create("ping", CommandType.CtxMsg) + expect(bothCmdId).toBe("ping_A3") +}) +test('id -> CtxUsr', () => { + const bothCmdId = Id.create("ping", CommandType.CtxUser) + expect(bothCmdId).toBe("ping_A2") +}) +test('id -> Modal', () => { + const modal = Id.create("my-modal", CommandType.Modal) + expect(modal).toBe("my-modal_M"); +}) + +test('id -> Button', () => { + const modal = Id.create("my-button", CommandType.Button) + expect(modal).toBe("my-button_C2"); +}) + +test('id -> Slash', () => { + const modal = Id.create("myslash", CommandType.Slash) + expect(modal).toBe("myslash_A1"); +}) + +test('id -> StringSelect', () => { + const modal = Id.create("mystringselect", CommandType.StringSelect) + expect(modal).toBe("mystringselect_C3"); +}) + +test('id -> UserSelect', () => { + const modal = Id.create("myuserselect", CommandType.UserSelect) + expect(modal).toBe("myuserselect_C5"); +}) + +test('id -> RoleSelect', () => { + const modal = Id.create("myroleselect", CommandType.RoleSelect) + expect(modal).toBe("myroleselect_C6"); +}) + +test('id -> MentionSelect', () => { + const modal = Id.create("mymentionselect", CommandType.MentionableSelect) + expect(modal).toBe("mymentionselect_C7"); +}) + +test('id -> ChannelSelect', () => { + const modal = Id.create("mychannelselect", CommandType.ChannelSelect) + expect(modal).toBe("mychannelselect_C8"); +}) + + + diff --git a/test/core/ioc.test.ts b/test/core/ioc.test.ts index 48f4d8ad..de8015c1 100644 --- a/test/core/ioc.test.ts +++ b/test/core/ioc.test.ts @@ -1,12 +1,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CoreContainer } from '../../src/core/ioc/container'; import { EventEmitter } from 'events'; -import { DefaultLogging, Disposable, Init, Logging } from '../../src/core'; +import { DefaultLogging, Disposable, Emitter, Init, Logging } from '../../src/core'; import { CoreDependencies } from '../../src/types/ioc'; describe('ioc container', () => { let container: CoreContainer<{}> = new CoreContainer(); let dependency: Logging & Init & Disposable; + let dependency2: Emitter beforeEach(() => { dependency = { init: vi.fn(), @@ -16,6 +17,11 @@ describe('ioc container', () => { debug(): void {}, dispose: vi.fn() }; + dependency2 = { + addListener: vi.fn(), + removeListener: vi.fn(), + emit: vi.fn() + }; container = new CoreContainer(); }); const wait = (seconds: number) => new Promise((resolve) => setTimeout(resolve, seconds)); @@ -83,4 +89,13 @@ describe('ioc container', () => { container.ready(); expect(dependency.init).toHaveBeenCalledTimes(0); }); + + it('should init dependency depending on something else', () => { + container.add({ '@sern/client': dependency2 }); + container.upsert((cntr) => ({ + '@sern/logger': dependency + })); + container.ready(); + expect(dependency.init).toHaveBeenCalledTimes(1); + }) }); diff --git a/test/core/services.test.ts b/test/core/services.test.ts index fea60460..479adef1 100644 --- a/test/core/services.test.ts +++ b/test/core/services.test.ts @@ -74,4 +74,7 @@ describe('services', () => { expect(consoleMock).toHaveBeenCalledOnce(); expect(consoleMock).toHaveBeenLastCalledWith({ message: 'error' }); }); + + + }); diff --git a/yarn.lock b/yarn.lock index 701090b2..afae7762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -629,7 +629,6 @@ __metadata: "@typescript-eslint/parser": 5.59.1 callsites: ^3.1.0 discord.js: ^14.11.0 - esbuild: ^0.17.0 eslint: 8.39.0 iti: ^0.6.0 prettier: 2.8.8 @@ -1449,7 +1448,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.17.0, esbuild@npm:^0.17.6": +"esbuild@npm:^0.17.6": version: 0.17.19 resolution: "esbuild@npm:0.17.19" dependencies: