mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
refactor: move metadata outside of module declarations
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { CommandModule } from '../types/modules';
|
||||
import { CommandMeta, CommandModule, Module } from '../types/modules';
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
export interface ModuleManager {
|
||||
get(id: string): string | undefined;
|
||||
getMetadata(m: Module): CommandMeta;
|
||||
setMetadata(m: Module, c: CommandMeta): void;
|
||||
set(id: string, path: string): void;
|
||||
getPublishableCommands(): Promise<CommandModule[]>;
|
||||
remove(id: string) : boolean
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { CommandMeta, Module } from "../types/modules";
|
||||
|
||||
/**
|
||||
* Represents a core module store that stores IDs mapped to file paths.
|
||||
*/
|
||||
export interface CoreModuleStore {
|
||||
commands : Map<string, string>;
|
||||
metadata : WeakMap<Module, CommandMeta>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { readdir, stat } from 'fs/promises';
|
||||
import { basename, extname, join, resolve } from 'path';
|
||||
import { ImportPayload } from '../handler/types';
|
||||
import * as assert from 'node:assert';
|
||||
import { sernMeta } from '../handler/commands';
|
||||
import { clazz } from '../handler/commands';
|
||||
|
||||
export type ModuleResult<T> = Promise<Result<ImportPayload<T>, SernError>>;
|
||||
|
||||
@@ -18,14 +18,16 @@ export async function importModule<T>(absPath: string) {
|
||||
/// #endif
|
||||
}
|
||||
export async function defaultModuleLoader<T extends Module>(absPath: string): ModuleResult<T> {
|
||||
const module = await importModule<T>(absPath);
|
||||
let module = await importModule<T>(absPath);
|
||||
if (module === undefined) {
|
||||
return Err(SernError.UndefinedModule);
|
||||
}
|
||||
if(Reflect.has(module, clazz)) {
|
||||
//@ts-ignore
|
||||
module = module.getInstance();
|
||||
}
|
||||
//todo readd class modules
|
||||
assert.ok(module.type > 0 && module.type < 1<<10, 'Found a module that does not have a valid type');
|
||||
assert.ok(module[sernMeta], "Found a module that isn't marked with sernMeta");
|
||||
|
||||
return Ok({ module, absPath });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import { CoreModuleStore, ModuleManager } from '../../contracts';
|
||||
import { importModule } from '../../module-loading';
|
||||
import { CommandModule } from '../../types/modules';
|
||||
|
||||
import { CommandMeta, CommandModule, Module } from '../../types/modules';
|
||||
/**
|
||||
* @internal
|
||||
* @since 2.0.0/*
|
||||
* @since 2.0.0
|
||||
* Version 4.0.0 will internalize this api. Please refrain from using ModuleStore!
|
||||
*/
|
||||
export class DefaultModuleManager implements ModuleManager {
|
||||
constructor(private moduleStore: CoreModuleStore) {}
|
||||
setMetadata(m: Module, c: CommandMeta): void {
|
||||
this.moduleStore.metadata.set(m, c);
|
||||
}
|
||||
|
||||
getMetadata(m: Module): CommandMeta {
|
||||
const maybeModule = this.moduleStore.metadata.get(m);
|
||||
if(!maybeModule) {
|
||||
throw Error("Could not find metadata in store for " + maybeModule);
|
||||
}
|
||||
return maybeModule;
|
||||
}
|
||||
|
||||
remove(id: string): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
@@ -26,13 +26,12 @@ import {
|
||||
import { CommandType, Context, EventType } from '../structures';
|
||||
import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './plugins';
|
||||
import { Awaitable, SernEventsMapping } from '../../shared';
|
||||
import { sernMeta } from '../../handler/commands';
|
||||
import { Processed } from '../../handler/types';
|
||||
import { Args, SlashOptions } from '../../shared';
|
||||
|
||||
|
||||
|
||||
interface CommandMeta {
|
||||
export interface CommandMeta {
|
||||
fullPath: string;
|
||||
id: string;
|
||||
}
|
||||
@@ -45,7 +44,6 @@ export interface Module {
|
||||
onEvent: ControlPlugin[];
|
||||
plugins: InitPlugin[];
|
||||
description?: string;
|
||||
[sernMeta]: CommandMeta;
|
||||
execute: (...args: any[]) => Awaitable<any>;
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ export interface ModalSubmitCommand extends Module {
|
||||
}
|
||||
|
||||
export interface AutocompleteCommand
|
||||
extends Omit<Module, 'name' | 'type' | 'plugins' | 'description' | typeof sernMeta> {
|
||||
extends Omit<Module, 'name' | 'type' | 'plugins' | 'description'> {
|
||||
onEvent: ControlPlugin[];
|
||||
execute: (ctx: AutocompleteInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
@@ -191,10 +189,10 @@ export interface SernAutocompleteData
|
||||
}
|
||||
|
||||
export type CommandModuleNoPlugins = {
|
||||
[T in CommandType]: Omit<CommandModuleDefs[T], 'plugins' | 'onEvent' | typeof sernMeta>;
|
||||
[T in CommandType]: Omit<CommandModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
export type EventModulesNoPlugins = {
|
||||
[T in EventType]: Omit<EventModuleDefs[T], 'plugins' | 'onEvent' | typeof sernMeta>;
|
||||
[T in EventType]: Omit<EventModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
|
||||
export type InputEvent = {
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
import { ClientEvents } from 'discord.js';
|
||||
import { CommandType, EventType, PluginType } from '../core/structures';
|
||||
import { AnyEventPlugin, ControlPlugin, InitPlugin, Plugin } from '../core/types/plugins';
|
||||
import { AnyCommandPlugin, AnyEventPlugin, CommandArgs, EventArgs } from '../core/types/plugins';
|
||||
import { CommandModule, EventModule, InputCommand, InputEvent } from '../core/types/modules';
|
||||
import { partition } from '../core/functions';
|
||||
import { partitionPlugins } from '../core/functions';
|
||||
import { Awaitable } from '../shared';
|
||||
export const sernMeta = Symbol('@sern/meta');
|
||||
export const UNREGISTERED = 'meow meow meow';
|
||||
export const EMPTY_PATH = 'purr purr purr';
|
||||
|
||||
|
||||
export const clazz = Symbol('@sern/class');
|
||||
/**
|
||||
* @since 1.0.0 The wrapper function to define command modules for sern
|
||||
* @param mod
|
||||
*/
|
||||
export function commandModule(mod: InputCommand): CommandModule {
|
||||
const [onEvent, plugins] = partition(
|
||||
mod.plugins ?? [],
|
||||
el => (el as Plugin).type === PluginType.Control,
|
||||
);
|
||||
const [onEvent, plugins] = partitionPlugins(mod.plugins);
|
||||
return {
|
||||
...mod,
|
||||
onEvent,
|
||||
plugins,
|
||||
[sernMeta]: {
|
||||
id: UNREGISTERED,
|
||||
fullPath: EMPTY_PATH
|
||||
}
|
||||
} as CommandModule;
|
||||
}
|
||||
/**
|
||||
@@ -32,17 +25,10 @@ export function commandModule(mod: InputCommand): CommandModule {
|
||||
* @param mod
|
||||
*/
|
||||
export function eventModule(mod: InputEvent): EventModule {
|
||||
const [onEvent, plugins] = partition(
|
||||
mod.plugins ?? [],
|
||||
el => (el as Plugin).type === PluginType.Control,
|
||||
);
|
||||
const [onEvent, plugins] = partitionPlugins(mod.plugins);
|
||||
return {
|
||||
onEvent,
|
||||
plugins,
|
||||
[sernMeta]: {
|
||||
id: UNREGISTERED,
|
||||
fullPath: EMPTY_PATH
|
||||
},
|
||||
...mod,
|
||||
} as EventModule;
|
||||
}
|
||||
@@ -64,32 +50,58 @@ export function discordEvent<T extends keyof ClientEvents>(mod: {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Class modules:
|
||||
// Can be refactored.
|
||||
// Both implement singleton, could I make them inherit a singleton parent class?
|
||||
/**
|
||||
* @Experimental
|
||||
* Will be refactored / changed in future
|
||||
*/
|
||||
export abstract class CommandExecutable<Type extends CommandType> {
|
||||
export abstract class CommandExecutable<const Type extends CommandType> {
|
||||
abstract type: Type;
|
||||
[sernMeta] = {
|
||||
id: UNREGISTERED,
|
||||
fullPath: EMPTY_PATH
|
||||
};
|
||||
plugins: InitPlugin[] = [];
|
||||
onEvent: ControlPlugin[] = [];
|
||||
abstract execute() : Awaitable<unknown>
|
||||
plugins?: AnyCommandPlugin[];
|
||||
private static _instance : CommandModule;
|
||||
static readonly [clazz] = true;
|
||||
constructor() {
|
||||
const [onEvent, plugins] = partitionPlugins(this.plugins);
|
||||
this.plugins = plugins as AnyCommandPlugin[];
|
||||
Reflect.set(this, 'onEvent', onEvent);
|
||||
}
|
||||
static getInstance() {
|
||||
if (!CommandExecutable._instance) {
|
||||
//@ts-ignore
|
||||
CommandExecutable._instance = new this();
|
||||
}
|
||||
return CommandExecutable._instance;
|
||||
}
|
||||
abstract execute(...args: CommandArgs<Type, PluginType.Control>) : Awaitable<unknown>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @Experimental
|
||||
* Will be refactored in future
|
||||
*/
|
||||
export abstract class EventExecutable<Type extends EventType> {
|
||||
abstract type: Type;
|
||||
[sernMeta] = {
|
||||
id: UNREGISTERED,
|
||||
fullPath: EMPTY_PATH
|
||||
};
|
||||
plugins: InitPlugin[] = [];
|
||||
onEvent: ControlPlugin[] = [];
|
||||
abstract execute(): Awaitable<unknown>;
|
||||
plugins?: AnyEventPlugin[];
|
||||
static readonly [clazz] = true;
|
||||
private static _instance : EventModule;
|
||||
constructor() {
|
||||
const [onEvent, plugins] = partitionPlugins(this.plugins);
|
||||
this.plugins = plugins as AnyEventPlugin[];
|
||||
Reflect.set(this, 'onEvent', onEvent);
|
||||
}
|
||||
static getInstance() {
|
||||
if (!EventExecutable._instance) {
|
||||
//@ts-ignore
|
||||
EventExecutable._instance = new this();
|
||||
}
|
||||
return EventExecutable._instance;
|
||||
}
|
||||
abstract execute(...args: EventArgs<Type, PluginType.Control>): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ObservableInput, pipe, switchMap } from 'rxjs';
|
||||
import { SernEmitter } from '../../core';
|
||||
import { errTap } from '../../core/operators';
|
||||
import * as Files from '../../core/module-loading';
|
||||
import { sernMeta } from '../commands';
|
||||
import { Err, Result } from 'ts-results-es';
|
||||
import { fmt } from './messages';
|
||||
import { ControlPlugin, VoidResult } from '../../core/types/plugins';
|
||||
@@ -73,13 +72,14 @@ export function createMessageHandler(
|
||||
* IMPURE SIDE EFFECT
|
||||
* This function assigns remaining, incomplete data to each imported module.
|
||||
*/
|
||||
function assignDefaults<T extends Module>(): MonoTypeOperatorFunction<ImportPayload<T>> {
|
||||
function assignDefaults<T extends Module>(moduleManager: ModuleManager): MonoTypeOperatorFunction<ImportPayload<T>> {
|
||||
return tap(
|
||||
({ module, absPath }) => {
|
||||
module.name ??= Files.filename(absPath);
|
||||
module.description ??= '...';
|
||||
module[sernMeta].fullPath = absPath;
|
||||
module[sernMeta].id = `${module.name}_${uniqueId(module.type)}`;
|
||||
moduleManager.setMetadata(
|
||||
module, { fullPath: absPath, id: `${module.name}_${uniqueId(module.type)}` }
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -87,13 +87,14 @@ function assignDefaults<T extends Module>(): MonoTypeOperatorFunction<ImportPayl
|
||||
export function buildModules<T extends AnyModule>(
|
||||
input: ObservableInput<string>,
|
||||
sernEmitter: SernEmitter,
|
||||
moduleManager: ModuleManager
|
||||
) {
|
||||
return pipe(
|
||||
switchMap(() => Files.buildModuleStream<T>(input)),
|
||||
errTap(error => {
|
||||
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
assignDefaults<T>()
|
||||
assignDefaults<T>(moduleManager)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,7 +147,7 @@ export function executeModule(
|
||||
* A higher order function that
|
||||
* - creates a stream of {@link VoidResult} { config.createStream }
|
||||
* - any failures results to { config.onFailure } being called
|
||||
* - if all results are ok, the stream is converted to { config.onSuccess }
|
||||
* - if all results are ok, the stream is converted to { config.onNext }
|
||||
* emit config.onSuccess Observable
|
||||
* @param config
|
||||
* @returns receiver function for flattening a stream of data
|
||||
|
||||
@@ -4,7 +4,6 @@ import { SernError } from '../../core/structures/errors';
|
||||
import { Result } from 'ts-results-es';
|
||||
import { ModuleManager } from '../../core/contracts';
|
||||
import { SernEmitter } from '../../core';
|
||||
import { sernMeta } from '../commands';
|
||||
import { Processed, DependencyList } from '../types';
|
||||
import { buildModules, callInitPlugins } from './generic';
|
||||
import { AnyModule } from '../../core/types/modules';
|
||||
@@ -16,7 +15,7 @@ export function startReadyEvent(
|
||||
const ready$ = fromEvent(client!, 'ready').pipe(take(1));
|
||||
return ready$
|
||||
.pipe(
|
||||
buildModules<Processed<AnyModule>>(allPaths, sEmitter),
|
||||
buildModules<Processed<AnyModule>>(allPaths, sEmitter, moduleManager),
|
||||
callInitPlugins({
|
||||
onStop: module => {
|
||||
sEmitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
@@ -39,7 +38,9 @@ function registerModule<T extends Processed<AnyModule>>(
|
||||
manager: ModuleManager,
|
||||
module: T,
|
||||
): Result<void, void> {
|
||||
const { id, fullPath } = module[sernMeta];
|
||||
|
||||
const { id, fullPath } = manager.getMetadata(module);
|
||||
|
||||
if (module.type === CommandType.Both
|
||||
|| module.type === CommandType.Text
|
||||
) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Dependencies } from '../../core/ioc/types';
|
||||
|
||||
|
||||
export function makeEventsHandler(
|
||||
[emitter, err, log,, client]: DependencyList,
|
||||
[emitter, err, log, moduleManager, client]: DependencyList,
|
||||
allPaths: ObservableInput<string>,
|
||||
) {
|
||||
|
||||
@@ -34,7 +34,7 @@ export function makeEventsHandler(
|
||||
};
|
||||
of(null)
|
||||
.pipe(
|
||||
buildModules<Processed<EventModule>>(allPaths, emitter),
|
||||
buildModules<Processed<EventModule>>(allPaths, emitter, moduleManager),
|
||||
callInitPlugins({
|
||||
onStop: module =>
|
||||
emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
|
||||
|
||||
Reference in New Issue
Block a user