diff --git a/package.json b/package.json index 1ed4016..0e3faf4 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,9 @@ "author": "SernDevs", "license": "MIT", "dependencies": { - "@sern/ioc": "^1.0.3", + "@sern/ioc": "^1.0.4", "callsites": "^3.1.0", + "deepmerge": "^4.3.1", "node-cron": "^3.0.3", "rxjs": "^7.8.0", "ts-results-es": "^4.1.0" diff --git a/src/core/modules.ts b/src/core/modules.ts index 84d389e..8d9d6f1 100644 --- a/src/core/modules.ts +++ b/src/core/modules.ts @@ -14,11 +14,10 @@ import type { Awaitable } from '../types/utility'; */ export function commandModule(mod: InputCommand): Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); - return { - ...mod, - onEvent, - plugins, - } as Module; + return { ...mod, + onEvent, + plugins, + locals: {} } as Module; } /** @@ -29,7 +28,9 @@ export function commandModule(mod: InputCommand): Module { export function eventModule(mod: InputEvent): Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); if(onEvent.length !== 0) throw Error("Event modules cannot have ControlPlugins"); - return { ...mod, plugins } as Module; + return { ...mod, + plugins, + locals: {} } as Module; } /** Create event modules from discord.js client events, diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index f835281..2f2f17a 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -17,6 +17,7 @@ import { CommandType } from '../core/structures/enums' import { inspect } from 'node:util' import { disposeAll } from '../core/ioc'; import { resultPayload, isAutocomplete, treeSearch, fmt } from '../core/functions' +import merge from 'deepmerge' function handleError(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) { return (pload: unknown, caught: Observable) => { @@ -213,25 +214,26 @@ export function createResultResolver(config: { }; }; -export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean) { - let _module = module; +function isObject(item: unknown) { + return (item && typeof item === 'object' && !Array.isArray(item)); +} + +//_module is frozen, preventing from mutations +export async function callInitPlugins(_module: Module, deps: Dependencies, emit?: boolean) { + let module = _module; const emitter = deps['@sern/emitter']; - for(const plugin of _module.plugins ?? []) { - const res = await plugin.execute({ - module: _module, - absPath: _module.meta.absPath, - deps - }); - if (!res) throw Error("Plugin did not return anything. " + inspect(plugin, false, Infinity, true)); - if(res.isErr()) { + for(const plugin of module.plugins ?? []) { + const result = await plugin.execute({ module, absPath: module.meta.absPath, deps }); + if (!result) throw Error("Plugin did not return anything. " + inspect(plugin, false, Infinity, true)); + if(result.isErr()) { if(emit) { emitter?.emit('module.register', - resultPayload('failure', module, res.error ?? SernError.PluginFailure)); + resultPayload('failure', module, result.error ?? SernError.PluginFailure)); } - throw Error(res.error ?? SernError.PluginFailure); + throw Error(result.error ?? SernError.PluginFailure); } } - return _module + return module } async function callPlugins({ args, module, deps, params }: ExecutePayload) { @@ -241,8 +243,8 @@ async function callPlugins({ args, module, deps, params }: ExecutePayload) { if(result.isErr()) { return result; } - if(typeof result.value === 'object' && result.value !== null) { - state = { ...state, ...result.value, }; + if(isObject(result.value)) { + state = merge(state, result.value!); } } return Ok(state); diff --git a/src/handlers/ready.ts b/src/handlers/ready.ts index 662b1bd..83f485c 100644 --- a/src/handlers/ready.ts +++ b/src/handlers/ready.ts @@ -23,8 +23,8 @@ export default async function(dir: string, deps : UnpackedDependencies) { if(!validType) { throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``); } - const resultModule = await callInitPlugins(module, deps, true); - // FREEZE! no more writing!! + const resultModule = await callInitPlugins(module, deps, true); // FREEZE! no more writing!! + commands.set(resultModule.meta.id, Object.freeze(resultModule)); sEmitter.emit('module.register', resultPayload('success', resultModule)); } diff --git a/src/types/core-modules.ts b/src/types/core-modules.ts index 1b302d6..8437fe2 100644 --- a/src/types/core-modules.ts +++ b/src/types/core-modules.ts @@ -18,7 +18,7 @@ import type { } from 'discord.js'; import type { CommandType, EventType } from '../core/structures/enums'; import { Context } from '../core/structures/context' -import { AnyPlugin, ControlPlugin, InitPlugin } from './core-plugin'; +import { ControlPlugin, InitPlugin, Plugin } from './core-plugin'; import { Awaitable, SernEventsMapping } from './utility'; //state, deps, type (very original) @@ -41,6 +41,7 @@ export interface Module { id: string; absPath: string; } + locals: Record execute(...args: any[]): Awaitable; } @@ -191,10 +192,10 @@ export interface SernAutocompleteData } type CommandModuleNoPlugins = { - [T in CommandType]: Omit; + [T in CommandType]: Omit; }; type EventModulesNoPlugins = { - [T in EventType]: Omit ; + [T in EventType]: Omit ; }; export type InputEvent = { @@ -206,7 +207,7 @@ export type InputEvent = { export type InputCommand = { [T in CommandType]: CommandModuleNoPlugins[T] & { - plugins?: AnyPlugin[]; + plugins?: Plugin[]; }; }[CommandType]; diff --git a/src/types/core-plugin.ts b/src/types/core-plugin.ts index 7b0c948..b63e23b 100644 --- a/src/types/core-plugin.ts +++ b/src/types/core-plugin.ts @@ -33,7 +33,6 @@ import type { } from 'discord.js'; export type PluginResult = Awaitable|undefined, string|undefined>>; - export interface InitArgs = Processed> { module: T; absPath: string; @@ -46,6 +45,7 @@ export interface Plugin { export interface InitPlugin extends Plugin { type: PluginType.Init; + execute: (...args: Args) => PluginResult; } export interface ControlPlugin extends Plugin { type: PluginType.Control; diff --git a/src/types/dependencies.d.ts b/src/types/dependencies.d.ts index ecc76d5..a66dedd 100644 --- a/src/types/dependencies.d.ts +++ b/src/types/dependencies.d.ts @@ -7,6 +7,21 @@ import { CoreDependencies } from './ioc'; declare global { + /** + * discord.js client. + * '@sern/client': Client + * sern emitter listens to events that happen throughout + * the handler. some include module.register, module.activate. + * '@sern/emitter': Contracts.Emitter; + * An error handler which is the final step before + * the sern process actually crashes. + '@sern/errors': Contracts.ErrorHandling; + * Optional logger. Performs ... logging + * '@sern/logger'?: Contracts.Logging; + * Readonly module store. sern stores these + * by module.meta.id -> Module + * '@sern/modules': Map; + */ interface Dependencies extends CoreDependencies {} } diff --git a/src/types/ioc.ts b/src/types/ioc.ts index 6e543ac..d9095c3 100644 --- a/src/types/ioc.ts +++ b/src/types/ioc.ts @@ -6,10 +6,28 @@ import { Module } from './core-modules'; export interface CoreDependencies { + /** + * discord.js client. + */ '@sern/client': Client; + /** + * sern emitter listens to events that happen throughout + * the handler. some include module.register, module.activate. + */ '@sern/emitter': Contracts.Emitter; + /** + * An error handler which is the final step before + * the sern process actually crashes. + */ '@sern/errors': Contracts.ErrorHandling; + /** + * Optional logger. Performs ... logging + */ '@sern/logger'?: Contracts.Logging; + /** + * Readonly module store. sern stores these + * by module.meta.id -> Module + */ '@sern/modules': Map; } diff --git a/test/handlers.test.ts b/test/handlers.test.ts index 35e6c02..a00117b 100644 --- a/test/handlers.test.ts +++ b/test/handlers.test.ts @@ -57,13 +57,8 @@ vi.mock('discord.js', async (importOriginal) => { function createRandomPlugin (s: 'go', mut?: Partial) { return CommandInitPlugin(({ module }) => { - if(mut) { - for(const [k,v] of Object.entries(mut)) { - module[k] = v - } - } return s == 'go' - ? controller.next() + ? controller.next(mut) : controller.stop() }) } @@ -103,16 +98,22 @@ describe('eventDispatcher standard', () => { }); }); -test ('mutate with init plugins', async () => { +test ('call init plugins', async () => { const deps = mockDeps() const plugins = createRandomPlugin('go', { name: "abc" }) const mod = createRandomModule([plugins]) - console.log(mod) const s = await callInitPlugins(mod, deps, false) - console.log(s) expect("abc").equal(s.name) }) +test('init plugins replace array', async () => { + const deps = mockDeps() + const plugins = createRandomPlugin('go', { opts: [] }) + const plugins2 = createRandomPlugin('go', { opts: ['a'] }) + const mod = createRandomModule([plugins, plugins2]) + const s = await callInitPlugins(mod, deps, false) + expect(['a']).deep.equal(s.opts) +}) test('call control plugin ', async () => { const plugin = CommandControlPlugin((ctx,sdt) => { diff --git a/yarn.lock b/yarn.lock index 6ffc01f..73fcc0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -553,12 +553,13 @@ __metadata: resolution: "@sern/handler@workspace:." dependencies: "@faker-js/faker": ^8.0.1 - "@sern/ioc": ^1.0.3 + "@sern/ioc": ^1.0.4 "@types/node": ^20.0.0 "@types/node-cron": ^3.0.11 "@typescript-eslint/eslint-plugin": 5.58.0 "@typescript-eslint/parser": 5.59.1 callsites: ^3.1.0 + deepmerge: ^4.3.1 discord.js: ^14.x.x eslint: 8.39.0 node-cron: ^3.0.3 @@ -569,10 +570,10 @@ __metadata: languageName: unknown linkType: soft -"@sern/ioc@npm:^1.0.3": - version: 1.0.3 - resolution: "@sern/ioc@npm:1.0.3" - checksum: 2c640cabbf3927d923b57233e660be1d6d4da1cd376a4ba77b118fd9aa5ef81c465d287357a15a7cd7827306dd614c73bde68751978d2030c3a4877dcbb0d02d +"@sern/ioc@npm:^1.0.4": + version: 1.0.4 + resolution: "@sern/ioc@npm:1.0.4" + checksum: 3d1a63099b3e8ff0d44bb73007b1d66c3f3b27cf7a193c2d9122e021cb72be1ced535ea98efaf72602f371a489177e3b144ed72da8d7de80887c0408ce79cce2 languageName: node linkType: hard @@ -1172,6 +1173,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3"