mirror of
https://github.com/sern-handler/handler
synced 2026-06-15 12:22:14 +00:00
refactor: minor (#347)
* some refactoring * accidental merge * refactor: ensure all asserts have error message to avoid cryptic messages * general refactoring * move controller to create-plugin
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { CommandType, EventType, PluginType } from './structures';
|
||||
import type { Plugin, PluginResult, EventArgs, CommandArgs } from '../types/core-plugin';
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
import { err, ok } from './functions';
|
||||
|
||||
export function makePlugin<V extends unknown[]>(
|
||||
type: PluginType,
|
||||
@@ -60,3 +61,12 @@ export function DiscordEventControlPlugin<T extends keyof ClientEvents>(
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* The object passed into every plugin to control a command's behavior
|
||||
*/
|
||||
export const controller = {
|
||||
next: ok,
|
||||
stop: err,
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ export function treeSearch(
|
||||
const choice = iAutocomplete.options.getFocused(true);
|
||||
assert(
|
||||
'command' in cur,
|
||||
'No command property found for autocomplete option',
|
||||
'No `command` property found for autocomplete option',
|
||||
);
|
||||
if (subcommands.size > 0) {
|
||||
const parent = iAutocomplete.options.getSubcommand();
|
||||
|
||||
@@ -42,6 +42,7 @@ export const CommandTypeDiscordApi = [
|
||||
ComponentType.MentionableSelect,
|
||||
ComponentType.ChannelSelect,
|
||||
];
|
||||
|
||||
/*
|
||||
* Generates a number based on CommandType.
|
||||
* This corresponds to an ApplicationCommandType or ComponentType
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CoreContainer } from './container';
|
||||
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<Partial<Dependencies>>;
|
||||
|
||||
@@ -22,6 +23,12 @@ export function useContainerRaw() {
|
||||
return containerSubject;
|
||||
}
|
||||
|
||||
export function disposeAll(logger: Logging|undefined) {
|
||||
containerSubject
|
||||
?.disposeAll()
|
||||
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
|
||||
}
|
||||
|
||||
const dependencyBuilder = (container: any, excluded: string[]) => {
|
||||
type Insertable =
|
||||
| ((container: CoreContainer<Dependencies>) => unknown )
|
||||
@@ -82,16 +89,16 @@ export const insertLogger = (containerSubject: CoreContainer<any>) => {
|
||||
}
|
||||
export async function makeDependencies<const T extends Dependencies>
|
||||
(conf: ValidDependencyConfig) {
|
||||
//Until there are more optional dependencies, just check if the logger exists
|
||||
//SIDE EFFECT
|
||||
containerSubject = new CoreContainer();
|
||||
if(typeof conf === 'function') {
|
||||
const excluded: string[] = [];
|
||||
conf(dependencyBuilder(containerSubject, excluded));
|
||||
|
||||
if(!excluded.includes('@sern/logger')
|
||||
&& !containerSubject.getTokens()['@sern/logger']) {
|
||||
insertLogger(containerSubject);
|
||||
}
|
||||
|
||||
containerSubject.ready();
|
||||
} else {
|
||||
composeRoot(containerSubject, conf);
|
||||
|
||||
@@ -22,19 +22,18 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
|
||||
|
||||
(this as Container<{}, {}>)
|
||||
.add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling(),
|
||||
'@sern/emitter': () => new SernEmitter(),
|
||||
'@sern/store': () => new ModuleStore() })
|
||||
'@sern/emitter': () => new SernEmitter,
|
||||
'@sern/store': () => new ModuleStore })
|
||||
.add(ctx => {
|
||||
return {
|
||||
'@sern/modules': () =>
|
||||
new DefaultServices.DefaultModuleManager(ctx['@sern/store']),
|
||||
};
|
||||
return { '@sern/modules': () =>
|
||||
new DefaultServices.DefaultModuleManager(ctx['@sern/store']) };
|
||||
});
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.ready$.closed;
|
||||
}
|
||||
|
||||
override async disposeAll() {
|
||||
|
||||
const otherDisposables = Object
|
||||
|
||||
@@ -130,7 +130,6 @@ export function loadConfig(wrapper: Wrapper | 'file'): Wrapper {
|
||||
eventsPath = makePath('events');
|
||||
console.log('Events path is set to', eventsPath);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
defaultPrefix: config.defaultPrefix,
|
||||
|
||||
@@ -93,9 +93,9 @@ export abstract class CommandExecutable<const Type extends CommandType = Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Will be removed in future
|
||||
*/
|
||||
* @deprecated
|
||||
* Will be removed in future
|
||||
*/
|
||||
export abstract class EventExecutable<Type extends EventType> {
|
||||
abstract type: Type;
|
||||
plugins: AnyEventPlugin[] = [];
|
||||
|
||||
@@ -28,16 +28,15 @@ export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
|
||||
return concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY));
|
||||
}
|
||||
|
||||
interface PluginExecutable {
|
||||
execute: (...args: unknown[]) => PluginResult;
|
||||
};
|
||||
/**
|
||||
* Calls any plugin with {args}.
|
||||
* @param args if an array, its spread and plugin called.
|
||||
*/
|
||||
export function callPlugin(args: unknown): OperatorFunction<
|
||||
{
|
||||
execute: (...args: unknown[]) => PluginResult;
|
||||
},
|
||||
VoidResult
|
||||
> {
|
||||
export function callPlugin(args: unknown): OperatorFunction<PluginExecutable, VoidResult>
|
||||
{
|
||||
return concatMap(async plugin => {
|
||||
if (Array.isArray(args)) {
|
||||
return plugin.execute(...args);
|
||||
@@ -79,8 +78,6 @@ export const filterTap = <K, R>(onErr: (e: R) => void): OperatorFunction<Result<
|
||||
}
|
||||
onErr(result.error);
|
||||
return EMPTY
|
||||
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { ReplyOptions } from "../../types/utility";
|
||||
import type { Logging } from "../contracts";
|
||||
|
||||
export interface Response {
|
||||
type: 'fail' | 'continue';
|
||||
body?: ReplyOptions;
|
||||
log?: { type: keyof Logging; message: unknown }
|
||||
}
|
||||
|
||||
export const of = () => {
|
||||
const payload = {
|
||||
type: 'fail',
|
||||
body: undefined,
|
||||
log : undefined
|
||||
} as Record<PropertyKey, unknown>
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {'fail' | 'continue'} p a status to determine if the error will
|
||||
* terminate your application or continue. Warning and
|
||||
*/
|
||||
status: (p: 'fail' | 'continue') => {
|
||||
payload.type = p;
|
||||
return payload;
|
||||
},
|
||||
/**
|
||||
* @param {keyof Logging} type Determine to log to logger[type].
|
||||
* @param {T} message the message to log
|
||||
*
|
||||
* Log this error with the logger.
|
||||
*/
|
||||
log: <T=string>(type: keyof Logging, message: T) => {
|
||||
payload.log = { type, message };
|
||||
return payload;
|
||||
},
|
||||
reply: (bodyContent: ReplyOptions) => {
|
||||
payload.body = bodyContent;
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -114,7 +114,7 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
|
||||
if ('interaction' in wrappable) {
|
||||
return new Context(Ok(wrappable));
|
||||
}
|
||||
assert.ok(wrappable.isChatInputCommand());
|
||||
assert.ok(wrappable.isChatInputCommand(), "Context created with bad interaction.");
|
||||
return new Context(Err(wrappable));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'node:assert';
|
||||
*/
|
||||
export abstract class CoreContext<M, I> {
|
||||
protected constructor(protected ctx: Either<M, I>) {
|
||||
assert.ok(typeof ctx === 'object' && ctx != null);
|
||||
assert.ok(typeof ctx === 'object' && ctx != null, "Context was nonobject or null");
|
||||
}
|
||||
get message(): M {
|
||||
return this.ctx.expect(SernError.MismatchEvent);
|
||||
|
||||
@@ -3,4 +3,3 @@ export * from './context';
|
||||
export * from './sern-emitter';
|
||||
export * from './services';
|
||||
export * from './module-store';
|
||||
export * as CommandError from './command-error';
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createResultResolver } from './event-utils';
|
||||
import { BaseInteraction, Message } from 'discord.js';
|
||||
import { CommandType, Context } from '../core';
|
||||
import type { AnyFunction, Args } from '../types/utility';
|
||||
import { inspect } from 'node:util'
|
||||
import type { CommandModule, Module, Processed } from '../types/core-modules';
|
||||
|
||||
//TODO: refactor dispatchers so that it implements a strategy for each different type of payload?
|
||||
@@ -75,10 +76,7 @@ export function createDispatcher(payload: {
|
||||
case CommandType.Both: {
|
||||
if (isAutocomplete(payload.event)) {
|
||||
const option = treeSearch(payload.event, payload.module.options);
|
||||
assert.ok(
|
||||
option,
|
||||
Error(SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`),
|
||||
);
|
||||
assert.ok(option, SernError.NotSupportedInteraction + ` There is no autocomplete tag for ` + inspect(payload.module));
|
||||
const { command, name, parent } = option;
|
||||
|
||||
return {
|
||||
|
||||
@@ -21,17 +21,17 @@ import {
|
||||
handleError,
|
||||
SernError,
|
||||
VoidResult,
|
||||
useContainerRaw,
|
||||
} from '../core/_internal';
|
||||
import { Emitter, ErrorHandling, Logging, ModuleManager } 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 { AnyFunction, Awaitable } from '../types/utility';
|
||||
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 { disposeAll } from '../core/ioc/base';
|
||||
|
||||
function createGenericHandler<Source, Narrowed extends Source, Output>(
|
||||
source: Observable<Source>,
|
||||
@@ -77,11 +77,10 @@ export function createInteractionHandler<T extends Interaction>(
|
||||
}
|
||||
return Files
|
||||
.defaultModuleLoader<Processed<CommandModule>>(fullPath)
|
||||
.then(payload =>
|
||||
Ok(createDispatcher({
|
||||
module: payload.module,
|
||||
event,
|
||||
})));
|
||||
.then(payload => Ok(createDispatcher({
|
||||
module: payload.module,
|
||||
event,
|
||||
})));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -100,10 +99,9 @@ export function createMessageHandler(
|
||||
}
|
||||
return Files
|
||||
.defaultModuleLoader<Processed<CommandModule>>(fullPath)
|
||||
.then((payload)=> {
|
||||
.then(payload => {
|
||||
const args = contextArgs(event, rest);
|
||||
return Ok({ args, ...payload });
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -172,6 +170,8 @@ export function executeModule(
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A higher order function that
|
||||
* - creates a stream of {@link VoidResult} { config.createStream }
|
||||
@@ -258,8 +258,5 @@ export const handleCrash = (err: ErrorHandling, log?: Logging) =>
|
||||
log?.info({
|
||||
message: 'A stream closed or reached end of lifetime',
|
||||
});
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => log?.info({ message: 'Cleaning container and crashing' }));
|
||||
}),
|
||||
);
|
||||
disposeAll(log);
|
||||
}));
|
||||
|
||||
@@ -28,6 +28,5 @@ export function interactionHandler([emitter, err, log, modules, client]: Depende
|
||||
filterTap(e => emitter.emit('warning', SernEmitter.warning(e))),
|
||||
makeModuleExecutor(module =>
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure))),
|
||||
mergeMap(payload => executeModule(emitter, log, err, payload)),
|
||||
);
|
||||
mergeMap(payload => executeModule(emitter, log, err, payload)));
|
||||
}
|
||||
|
||||
@@ -42,6 +42,5 @@ export function messageHandler(
|
||||
makeModuleExecutor(module => {
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
mergeMap(payload => executeModule(emitter, log, err, payload)),
|
||||
);
|
||||
mergeMap(payload => executeModule(emitter, log, err, payload)));
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@ export const presenceHandler = (path: string, setPresence: SetPresence) => {
|
||||
//concatMap resolves the promise, and passes it to the next concatMap.
|
||||
concatMap(fn => parseConfig(fn())),
|
||||
// subscribe to the observable parseConfig yields, and set the presence.
|
||||
concatMap(conf => conf.pipe(map(setPresence))))
|
||||
concatMap(conf => conf.pipe(map(setPresence))));
|
||||
}
|
||||
|
||||
@@ -55,4 +55,3 @@ export * as Presence from './core/presences'
|
||||
export {
|
||||
useContainerRaw
|
||||
} from './core/_internal'
|
||||
export { controller } from './sern';
|
||||
|
||||
@@ -66,11 +66,4 @@ function useDependencies() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* The object passed into every plugin to control a command's behavior
|
||||
*/
|
||||
export const controller = {
|
||||
next: ok,
|
||||
stop: err,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user