mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
style: pretty
This commit is contained in:
@@ -18,6 +18,4 @@ export interface ErrorHandling {
|
||||
* @param error
|
||||
*/
|
||||
updateAlive(error: Error): void;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ import { Awaitable } from '../../shared';
|
||||
* Let dependencies implement this to initiate some logic.
|
||||
*/
|
||||
export interface Init {
|
||||
init() : Awaitable<unknown>
|
||||
init(): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@ export interface ModuleManager {
|
||||
setMetadata(m: Module, c: CommandMeta): void;
|
||||
set(id: string, path: string): void;
|
||||
getPublishableCommands(): Promise<CommandModule[]>;
|
||||
remove(id: string) : boolean
|
||||
remove(id: string): boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CommandMeta, Module } from "../types/modules";
|
||||
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>
|
||||
commands: Map<string, string>;
|
||||
metadata: WeakMap<Module, CommandMeta>;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,19 @@ import { PluginType } from './structures';
|
||||
export const ok = /* @__PURE__*/ () => Ok.EMPTY;
|
||||
export const err = /* @__PURE__*/ () => Err.EMPTY;
|
||||
|
||||
export function partitionPlugins(arr: (AnyEventPlugin|AnyCommandPlugin)[] = []): [Plugin[], Plugin[]] {
|
||||
export function partitionPlugins(
|
||||
arr: (AnyEventPlugin | AnyCommandPlugin)[] = [],
|
||||
): [Plugin[], Plugin[]] {
|
||||
const controlPlugins = [];
|
||||
const initPlugins = [];
|
||||
for (const el of arr) {
|
||||
switch(el.type)
|
||||
{
|
||||
case PluginType.Control: controlPlugins.push(el); break;
|
||||
case PluginType.Init: initPlugins.push(el); break;
|
||||
switch (el.type) {
|
||||
case PluginType.Control:
|
||||
controlPlugins.push(el);
|
||||
break;
|
||||
case PluginType.Init:
|
||||
initPlugins.push(el);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [controlPlugins, initPlugins];
|
||||
@@ -39,22 +44,22 @@ export function treeSearch(
|
||||
switch (cur.type) {
|
||||
case ApplicationCommandOptionType.Subcommand:
|
||||
case ApplicationCommandOptionType.SubcommandGroup:
|
||||
{
|
||||
{
|
||||
for (const option of cur.options ?? []) {
|
||||
_options.push(option);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (cur.autocomplete) {
|
||||
const choice = iAutocomplete.options.getFocused(true);
|
||||
if (cur.name === choice.name && cur.autocomplete) {
|
||||
autocompleteData = cur;
|
||||
{
|
||||
if (cur.autocomplete) {
|
||||
const choice = iAutocomplete.options.getFocused(true);
|
||||
if (cur.name === choice.name && cur.autocomplete) {
|
||||
autocompleteData = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return autocompleteData;
|
||||
|
||||
@@ -24,12 +24,6 @@ export type {
|
||||
EventModuleDefs,
|
||||
BaseOptions,
|
||||
SernAutocompleteData,
|
||||
SernOptionsData
|
||||
SernOptionsData,
|
||||
} from './types/modules';
|
||||
export type {
|
||||
Controller,
|
||||
PluginResult,
|
||||
InitPlugin,
|
||||
ControlPlugin,
|
||||
Plugin
|
||||
} from './types/plugins';
|
||||
export type { Controller, PluginResult, InitPlugin, ControlPlugin, Plugin } from './types/plugins';
|
||||
|
||||
@@ -3,8 +3,7 @@ import { composeRoot, useContainer } from './dependency-injection';
|
||||
import { Dependencies, DependencyConfiguration } from './types';
|
||||
import { CoreContainer } from '../structures/container';
|
||||
|
||||
|
||||
//SIDE EFFECT: GLOBAL DI
|
||||
//SIDE EFFECT: GLOBAL DI
|
||||
let containerSubject: CoreContainer<Partial<Dependencies>>;
|
||||
|
||||
/**
|
||||
@@ -14,7 +13,7 @@ let containerSubject: CoreContainer<Partial<Dependencies>>;
|
||||
export function useContainerRaw() {
|
||||
assert.ok(
|
||||
containerSubject && containerSubject.isReady(),
|
||||
"Could not find container or container wasn't ready. Did you call makeDependencies?"
|
||||
"Could not find container or container wasn't ready. Did you call makeDependencies?",
|
||||
);
|
||||
return containerSubject;
|
||||
}
|
||||
@@ -33,4 +32,3 @@ export async function makeDependencies<const T extends Dependencies>(
|
||||
|
||||
return useContainer<T>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { DependencyConfiguration, MapDeps, IntoDependencies, Dependencies, CoreDependencies } from './types';
|
||||
import type {
|
||||
DependencyConfiguration,
|
||||
MapDeps,
|
||||
IntoDependencies,
|
||||
Dependencies,
|
||||
CoreDependencies,
|
||||
} from './types';
|
||||
import { DefaultLogging } from '../structures';
|
||||
import { SernError } from '../structures/errors';
|
||||
import { useContainerRaw } from './base';
|
||||
import { CoreContainer } from '../structures/container';
|
||||
|
||||
|
||||
/**
|
||||
* @__PURE__
|
||||
* @since 2.0.0.
|
||||
@@ -18,7 +23,7 @@ export function single<T>(cb: () => T) {
|
||||
/**
|
||||
* @__PURE__
|
||||
* @since 2.0.0
|
||||
* Creates a transient object
|
||||
* Creates a transient object
|
||||
* @param cb
|
||||
*/
|
||||
export function transient<T>(cb: () => () => T) {
|
||||
@@ -42,7 +47,7 @@ export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]
|
||||
*/
|
||||
export async function composeRoot(
|
||||
container: CoreContainer<Partial<Dependencies>>,
|
||||
conf: DependencyConfiguration
|
||||
conf: DependencyConfiguration,
|
||||
) {
|
||||
//container should have no client or logger yet.
|
||||
const hasLogger = conf.exclude?.has('@sern/logger');
|
||||
@@ -71,11 +76,7 @@ export function useContainer<const T extends Dependencies>() {
|
||||
Warning: using a container hook (useContainer) is not recommended.
|
||||
Could lead to many unwanted side effects.
|
||||
Use the new Service(s) api function instead.
|
||||
`
|
||||
);
|
||||
`);
|
||||
return <V extends (keyof T)[]>(...keys: [...V]) =>
|
||||
keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as MapDeps<T, V>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as Contract from '../contracts';
|
||||
export type Singleton<T> = () => T;
|
||||
export type Transient<T> = () => () => T;
|
||||
|
||||
|
||||
export interface CoreDependencies {
|
||||
'@sern/logger'?: Singleton<Contract.Logging>;
|
||||
'@sern/emitter': Singleton<import('../structures/sern-emitter').SernEmitter>;
|
||||
@@ -15,10 +14,10 @@ export interface CoreDependencies {
|
||||
export interface Dependencies extends CoreDependencies {
|
||||
'@sern/client': Singleton<import('node:events').EventEmitter>;
|
||||
}
|
||||
export type DependencyFromKey<T extends keyof Dependencies> = Dependencies[T];
|
||||
export type DependencyFromKey<T extends keyof Dependencies> = Dependencies[T];
|
||||
|
||||
export type IntoDependencies<Tuple extends [...any[]]> = {
|
||||
[Index in keyof Tuple]: UnpackFunction<DependencyFromKey<Tuple[Index]>&{}>; //Unpack and make NonNullable
|
||||
[Index in keyof Tuple]: UnpackFunction<DependencyFromKey<Tuple[Index]> & {}>; //Unpack and make NonNullable
|
||||
} & { length: Tuple['length'] };
|
||||
|
||||
export interface DependencyConfiguration {
|
||||
@@ -38,4 +37,3 @@ export type MapDeps<Deps extends Dependencies, T extends readonly unknown[]> = T
|
||||
...(MapDeps<Deps, Rest> extends [never] ? [] : MapDeps<Deps, Rest>),
|
||||
]
|
||||
: [never];
|
||||
|
||||
|
||||
@@ -9,19 +9,18 @@ import { CommandExecutable, clazz } from '../handler/commands';
|
||||
|
||||
export type ModuleResult<T> = Promise<Result<ImportPayload<T>, SernError>>;
|
||||
|
||||
|
||||
function isClassModule(m: unknown): m is typeof CommandExecutable {
|
||||
return m != undefined && Reflect.has(m, clazz);
|
||||
}
|
||||
|
||||
export async function importModule<T>(absPath: string) {
|
||||
let module =
|
||||
/// #if MODE === 'esm'
|
||||
import(absPath).then(i => i.default); // eslint-disable-line
|
||||
let module =
|
||||
/// #if MODE === 'esm'
|
||||
import(absPath).then(i => i.default); // eslint-disable-line
|
||||
/// #elif MODE === 'cjs'
|
||||
require(absPath).default; // eslint-disable-line
|
||||
/// #endif
|
||||
return module.then(m => isClassModule(m) ? m.getInstance():m) as T;
|
||||
return module.then(m => (isClassModule(m) ? m.getInstance() : m)) as T;
|
||||
}
|
||||
export async function defaultModuleLoader<T extends Module>(absPath: string): ModuleResult<T> {
|
||||
let module = await importModule<T>(absPath);
|
||||
@@ -32,7 +31,6 @@ export async function defaultModuleLoader<T extends Module>(absPath: string): Mo
|
||||
return Ok({ module, absPath });
|
||||
}
|
||||
|
||||
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
|
||||
/**
|
||||
@@ -64,19 +62,18 @@ async function* readPaths(dir: string, shouldDebug: boolean): AsyncGenerator<str
|
||||
const base = basename(file);
|
||||
if (fileStats.isDirectory()) {
|
||||
//Todo: refactor so that i dont repeat myself for files (line 71)
|
||||
if(base.endsWith('-ignore!')) {
|
||||
if(shouldDebug)
|
||||
console.info(`ignored directory: ${fullPath}`);
|
||||
if (base.endsWith('-ignore!')) {
|
||||
if (shouldDebug) console.info(`ignored directory: ${fullPath}`);
|
||||
} else {
|
||||
yield* readPaths(fullPath, shouldDebug);
|
||||
}
|
||||
} else {
|
||||
const isSkippable = fmtFileName(base).endsWith('-ignore!')
|
||||
|| !['.js', '.cjs', '.mts', '.mjs'].includes(extname(base));
|
||||
const isSkippable =
|
||||
fmtFileName(base).endsWith('-ignore!') ||
|
||||
!['.js', '.cjs', '.mts', '.mjs'].includes(extname(base));
|
||||
|
||||
if(isSkippable) {
|
||||
if(shouldDebug)
|
||||
console.info(`ignored: ${fullPath}`);
|
||||
if (isSkippable) {
|
||||
if (shouldDebug) console.info(`ignored: ${fullPath}`);
|
||||
} else {
|
||||
/// #if MODE === 'esm'
|
||||
yield 'file:///' + fullPath;
|
||||
@@ -90,4 +87,3 @@ async function* readPaths(dir: string, shouldDebug: boolean): AsyncGenerator<str
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,9 +97,7 @@ export const sharedObservable = <T>(e: EventEmitter, eventName: string) => {
|
||||
export function handleError<C>(crashHandler: ErrorHandling, logging?: Logging) {
|
||||
return (pload: unknown, caught: Observable<C>) => {
|
||||
// This is done to fit the ErrorHandling contract
|
||||
const err = pload instanceof Error
|
||||
? pload
|
||||
: Error(util.inspect(pload, { colors: true }));
|
||||
const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true }));
|
||||
if (crashHandler.keepAlive == 0) {
|
||||
crashHandler.crash(err);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Container } from 'iti';
|
||||
import { DefaultErrorHandling, DefaultModuleManager, SernEmitter } from '../';
|
||||
import { isAsyncFunction} from 'node:util/types';
|
||||
import { isAsyncFunction } from 'node:util/types';
|
||||
import * as assert from 'node:assert';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ModuleStore } from './module-store';
|
||||
@@ -22,34 +22,35 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
|
||||
'@sern/errors': () => new DefaultErrorHandling(),
|
||||
'@sern/emitter': () => new SernEmitter(),
|
||||
'@sern/store': () => new ModuleStore(),
|
||||
}).add(ctx => {
|
||||
})
|
||||
.add(ctx => {
|
||||
return { '@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']) };
|
||||
});
|
||||
}
|
||||
|
||||
private listenForInsertions() {
|
||||
assert.ok(!this.isReady(), 'listening for init functions should only occur prior to sern being ready.');
|
||||
const unsubscriber = this.on('containerUpserted', this.callInitHooks);
|
||||
|
||||
this.ready$.subscribe({
|
||||
complete: unsubscriber
|
||||
});
|
||||
private listenForInsertions() {
|
||||
assert.ok(
|
||||
!this.isReady(),
|
||||
'listening for init functions should only occur prior to sern being ready.',
|
||||
);
|
||||
const unsubscriber = this.on('containerUpserted', this.callInitHooks);
|
||||
|
||||
this.ready$.subscribe({
|
||||
complete: unsubscriber,
|
||||
});
|
||||
}
|
||||
|
||||
private async callInitHooks(e: { key: keyof T, newContainer: T[keyof T]|null }) {
|
||||
|
||||
private async callInitHooks(e: { key: keyof T; newContainer: T[keyof T] | null }) {
|
||||
const dep = e.newContainer;
|
||||
assert.ok(dep);
|
||||
//Ignore any dependencies that are not objects or array
|
||||
if(typeof(dep) !== 'object' || Array.isArray(dep)) {
|
||||
if (typeof dep !== 'object' || Array.isArray(dep)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if('init' in dep && typeof dep.init === 'function') {
|
||||
isAsyncFunction(dep.init)
|
||||
? await dep.init()
|
||||
: dep.init();
|
||||
}
|
||||
if ('init' in dep && typeof dep.init === 'function') {
|
||||
isAsyncFunction(dep.init) ? await dep.init() : dep.init();
|
||||
}
|
||||
}
|
||||
|
||||
isReady() {
|
||||
|
||||
@@ -92,4 +92,3 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
|
||||
function safeUnwrap<T>(res: Result<T, T>) {
|
||||
return res.val;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ import { Module, CommandMeta } from '../types/modules';
|
||||
* For interacting with modules, use the ModuleManager instead.
|
||||
*/
|
||||
export class ModuleStore implements CoreModuleStore {
|
||||
metadata = new WeakMap<Module, CommandMeta>();
|
||||
metadata = new WeakMap<Module, CommandMeta>();
|
||||
commands = new Map<string, string>();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Module } from '../types/modules';
|
||||
* @since 1.0.0
|
||||
*/
|
||||
export class SernEmitter extends EventEmitter {
|
||||
|
||||
constructor() {
|
||||
super({ captureRejections: true });
|
||||
}
|
||||
|
||||
@@ -3,42 +3,42 @@ import { CoreModuleStore, ModuleManager } from '../../contracts';
|
||||
import { importModule } from '../../module-loading';
|
||||
import { CommandMeta, CommandModule, Module } from '../../types/modules';
|
||||
/**
|
||||
* @internal
|
||||
* @since 2.0.0
|
||||
* Version 4.0.0 will internalize this api. Please refrain from using ModuleStore!
|
||||
*/
|
||||
* @internal
|
||||
* @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 {
|
||||
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.');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.moduleStore.commands.get(id);
|
||||
}
|
||||
set(id: string, path: string): void {
|
||||
this.moduleStore.commands.set(id, path);
|
||||
}
|
||||
//not tested
|
||||
getPublishableCommands(): Promise<CommandModule[]> {
|
||||
const entries = this.moduleStore.commands.entries();
|
||||
const publishable = 0b000000110;
|
||||
return Promise.all(
|
||||
Array.from(entries)
|
||||
.filter(([id]) => !(Number.parseInt(id.at(-1)!) & publishable))
|
||||
.map(([, path]) => importModule<CommandModule>(path))
|
||||
);
|
||||
}
|
||||
remove(id: string): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.moduleStore.commands.get(id);
|
||||
}
|
||||
set(id: string, path: string): void {
|
||||
this.moduleStore.commands.set(id, path);
|
||||
}
|
||||
//not tested
|
||||
getPublishableCommands(): Promise<CommandModule[]> {
|
||||
const entries = this.moduleStore.commands.entries();
|
||||
const publishable = 0b000000110;
|
||||
return Promise.all(
|
||||
Array.from(entries)
|
||||
.filter(([id]) => !(Number.parseInt(id.at(-1)!) & publishable))
|
||||
.map(([, path]) => importModule<CommandModule>(path)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ import { Awaitable, SernEventsMapping } from '../../shared';
|
||||
import { Processed } from '../../handler/types';
|
||||
import { Args, SlashOptions } from '../../shared';
|
||||
|
||||
|
||||
|
||||
export interface CommandMeta {
|
||||
fullPath: string;
|
||||
id: string;
|
||||
|
||||
@@ -12,11 +12,40 @@
|
||||
*/
|
||||
|
||||
import type { Err, Ok, Result } from 'ts-results-es';
|
||||
import type { BothCommand, ButtonCommand, ChannelSelectCommand, CommandModule, ContextMenuMsg, ContextMenuUser, DiscordEventCommand, EventModule, ExternalEventCommand, MentionableSelectCommand, ModalSubmitCommand, RoleSelectCommand, SernEventCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand } from './modules';
|
||||
import type {
|
||||
BothCommand,
|
||||
ButtonCommand,
|
||||
ChannelSelectCommand,
|
||||
CommandModule,
|
||||
ContextMenuMsg,
|
||||
ContextMenuUser,
|
||||
DiscordEventCommand,
|
||||
EventModule,
|
||||
ExternalEventCommand,
|
||||
MentionableSelectCommand,
|
||||
ModalSubmitCommand,
|
||||
RoleSelectCommand,
|
||||
SernEventCommand,
|
||||
SlashCommand,
|
||||
StringSelectCommand,
|
||||
TextCommand,
|
||||
UserSelectCommand,
|
||||
} from './modules';
|
||||
import { Args, Awaitable, Payload, SlashOptions } from '../../shared';
|
||||
import { CommandType, Context, EventType, PluginType } from '../structures';
|
||||
import { InitArgs, Processed } from '../../handler/types';
|
||||
import { ButtonInteraction, ChannelSelectMenuInteraction, ClientEvents, MentionableSelectMenuInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserContextMenuCommandInteraction, UserSelectMenuInteraction } from 'discord.js';
|
||||
import {
|
||||
ButtonInteraction,
|
||||
ChannelSelectMenuInteraction,
|
||||
ClientEvents,
|
||||
MentionableSelectMenuInteraction,
|
||||
MessageContextMenuCommandInteraction,
|
||||
ModalSubmitInteraction,
|
||||
RoleSelectMenuInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
UserSelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
|
||||
export type PluginResult = Awaitable<VoidResult>;
|
||||
export type VoidResult = Result<void, void>;
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { ClientEvents } from 'discord.js';
|
||||
import { CommandType, EventType, PluginType } from '../core/structures';
|
||||
import { AnyCommandPlugin, AnyEventPlugin, CommandArgs, ControlPlugin, EventArgs, InitPlugin } from '../core/types/plugins';
|
||||
import { CommandModule, EventModule, InputCommand, InputEvent, Module } from '../core/types/modules';
|
||||
import {
|
||||
AnyCommandPlugin,
|
||||
AnyEventPlugin,
|
||||
CommandArgs,
|
||||
ControlPlugin,
|
||||
EventArgs,
|
||||
InitPlugin,
|
||||
} from '../core/types/plugins';
|
||||
import {
|
||||
CommandModule,
|
||||
EventModule,
|
||||
InputCommand,
|
||||
InputEvent,
|
||||
Module,
|
||||
} from '../core/types/modules';
|
||||
import { partitionPlugins } from '../core/functions';
|
||||
import { Awaitable } from '../shared';
|
||||
|
||||
|
||||
export const clazz = Symbol('@sern/class');
|
||||
/**
|
||||
* @since 1.0.0 The wrapper function to define command modules for sern
|
||||
@@ -29,7 +41,7 @@ export function eventModule(mod: InputEvent): EventModule {
|
||||
return {
|
||||
...mod,
|
||||
plugins,
|
||||
onEvent
|
||||
onEvent,
|
||||
} as EventModule;
|
||||
}
|
||||
|
||||
@@ -66,20 +78,19 @@ function prepareClassPlugins(c: Module) {
|
||||
export abstract class CommandExecutable<const Type extends CommandType = CommandType> {
|
||||
abstract type: Type;
|
||||
plugins: AnyCommandPlugin[] = [];
|
||||
private static _instance : CommandModule;
|
||||
private static _instance: CommandModule;
|
||||
static readonly [clazz] = true;
|
||||
|
||||
static getInstance() {
|
||||
if (!CommandExecutable._instance) {
|
||||
//@ts-ignore
|
||||
CommandExecutable._instance = new this();
|
||||
prepareClassPlugins(CommandExecutable._instance);
|
||||
prepareClassPlugins(CommandExecutable._instance);
|
||||
}
|
||||
return CommandExecutable._instance;
|
||||
}
|
||||
|
||||
abstract execute(...args: CommandArgs<Type, PluginType.Control>) : Awaitable<unknown>
|
||||
|
||||
abstract execute(...args: CommandArgs<Type, PluginType.Control>): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +101,7 @@ export abstract class EventExecutable<Type extends EventType> {
|
||||
abstract type: Type;
|
||||
plugins: AnyEventPlugin[] = [];
|
||||
static readonly [clazz] = true;
|
||||
private static _instance : EventModule;
|
||||
private static _instance: EventModule;
|
||||
static getInstance() {
|
||||
if (!EventExecutable._instance) {
|
||||
//@ts-ignore
|
||||
@@ -98,8 +109,6 @@ export abstract class EventExecutable<Type extends EventType> {
|
||||
prepareClassPlugins(EventExecutable._instance);
|
||||
}
|
||||
return EventExecutable._instance;
|
||||
}
|
||||
}
|
||||
abstract execute(...args: EventArgs<Type, PluginType.Control>): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,12 +12,9 @@ import { Processed } from '../types';
|
||||
import { BothCommand, CommandModule, Module } from '../../core/types/modules';
|
||||
import { Args } from '../../shared';
|
||||
|
||||
export function dispatchInteraction<
|
||||
T extends CommandModule,
|
||||
V extends BaseInteraction | Message
|
||||
>(
|
||||
export function dispatchInteraction<T extends CommandModule, V extends BaseInteraction | Message>(
|
||||
payload: { module: Processed<T>; event: V },
|
||||
createArgs: (m: typeof payload.event) => unknown[]
|
||||
createArgs: (m: typeof payload.event) => unknown[],
|
||||
) {
|
||||
return {
|
||||
module: payload.module,
|
||||
@@ -28,11 +25,14 @@ export function dispatchInteraction<
|
||||
export function dispatchMessage(module: Processed<CommandModule>, args: [Context, Args]) {
|
||||
return {
|
||||
module,
|
||||
args
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
export function dispatchAutocomplete(payload: { module: Processed<BothCommand>, event: AutocompleteInteraction }) {
|
||||
export function dispatchAutocomplete(payload: {
|
||||
module: Processed<BothCommand>;
|
||||
event: AutocompleteInteraction;
|
||||
}) {
|
||||
const option = treeSearch(payload.event, payload.module.options);
|
||||
if (option !== undefined) {
|
||||
return {
|
||||
@@ -43,21 +43,14 @@ export function dispatchAutocomplete(payload: { module: Processed<BothCommand>,
|
||||
throw Error(
|
||||
SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function contextArgs(
|
||||
wrappable: Message | BaseInteraction,
|
||||
messageArgs?: string[],
|
||||
) {
|
||||
export function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[]) {
|
||||
const ctx = Context.wrap(wrappable);
|
||||
const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.options];
|
||||
return [ctx, args] as [Context, Args];
|
||||
}
|
||||
|
||||
|
||||
export function interactionArg<T extends BaseInteraction>(interaction: T) {
|
||||
return [interaction] as [T];
|
||||
}
|
||||
@@ -101,7 +94,11 @@ export function createDispatcher(payload: {
|
||||
}) {
|
||||
switch (payload.module.type) {
|
||||
case CommandType.Text:
|
||||
throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream. ' + payload.module);
|
||||
throw Error(
|
||||
SernError.MismatchEvent +
|
||||
' Found a text module in interaction stream. ' +
|
||||
payload.module,
|
||||
);
|
||||
case CommandType.Slash:
|
||||
case CommandType.Both: {
|
||||
if (isAutocomplete(payload.event)) {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { Interaction, Message } from 'discord.js';
|
||||
import {
|
||||
Interaction,
|
||||
Message,
|
||||
} from 'discord.js';
|
||||
import { EMPTY, Observable, concatMap, filter, from, of, throwError, tap, MonoTypeOperatorFunction } from 'rxjs';
|
||||
EMPTY,
|
||||
Observable,
|
||||
concatMap,
|
||||
filter,
|
||||
from,
|
||||
of,
|
||||
throwError,
|
||||
tap,
|
||||
MonoTypeOperatorFunction,
|
||||
} from 'rxjs';
|
||||
import { ModuleManager } from '../../core';
|
||||
import { SernError } from '../../core/structures/errors';
|
||||
import { callPlugin, everyPluginOk, filterMap, filterMapTo } from '../../core/operators';
|
||||
@@ -61,40 +68,40 @@ export function createMessageHandler(
|
||||
if (fullPath === undefined) {
|
||||
return Err(SernError.UndefinedModule + ' No full path found in module store');
|
||||
}
|
||||
return defaultModuleLoader<Processed<CommandModule>>(fullPath)
|
||||
.then(result => {
|
||||
const args = contextArgs(event, rest);
|
||||
return result.map(payload => dispatchMessage(payload.module, args));
|
||||
});
|
||||
return defaultModuleLoader<Processed<CommandModule>>(fullPath).then(result => {
|
||||
const args = contextArgs(event, rest);
|
||||
return result.map(payload => dispatchMessage(payload.module, args));
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* IMPURE SIDE EFFECT
|
||||
* This function assigns remaining, incomplete data to each imported module.
|
||||
*/
|
||||
function assignDefaults<T extends Module>(moduleManager: ModuleManager): MonoTypeOperatorFunction<ImportPayload<T>> {
|
||||
return tap(
|
||||
({ module, absPath }) => {
|
||||
* IMPURE SIDE EFFECT
|
||||
* This function assigns remaining, incomplete data to each imported module.
|
||||
*/
|
||||
function assignDefaults<T extends Module>(
|
||||
moduleManager: ModuleManager,
|
||||
): MonoTypeOperatorFunction<ImportPayload<T>> {
|
||||
return tap(({ module, absPath }) => {
|
||||
module.name ??= Files.filename(absPath);
|
||||
module.description ??= '...';
|
||||
moduleManager.setMetadata(
|
||||
module, { fullPath: absPath, id: `${module.name}_${uniqueId(module.type)}` }
|
||||
);
|
||||
}
|
||||
);
|
||||
moduleManager.setMetadata(module, {
|
||||
fullPath: absPath,
|
||||
id: `${module.name}_${uniqueId(module.type)}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function buildModules<T extends AnyModule>(
|
||||
input: ObservableInput<string>,
|
||||
sernEmitter: SernEmitter,
|
||||
moduleManager: ModuleManager
|
||||
moduleManager: ModuleManager,
|
||||
) {
|
||||
return pipe(
|
||||
switchMap(() => Files.buildModuleStream<T>(input)),
|
||||
errTap(error => {
|
||||
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
assignDefaults<T>(moduleManager)
|
||||
assignDefaults<T>(moduleManager),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,5 +213,3 @@ export function makeModuleExecutor<
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Interaction } from 'discord.js';
|
||||
import { concatMap, merge } from 'rxjs';
|
||||
import { concatMap, merge } from 'rxjs';
|
||||
import { SernError } from '../../core/structures/errors';
|
||||
import { SernEmitter } from '../../core';
|
||||
import { sharedObservable } from '../../core/operators';
|
||||
@@ -7,8 +7,7 @@ import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../co
|
||||
import { createInteractionHandler, executeModule, makeModuleExecutor } from './generic';
|
||||
import { DependencyList } from '../types';
|
||||
|
||||
export function makeInteractionHandler([emitter,,, modules, client]: DependencyList ) {
|
||||
|
||||
export function makeInteractionHandler([emitter, , , modules, client]: DependencyList) {
|
||||
const interactionStream$ = sharedObservable<Interaction>(client, 'interactionCreate');
|
||||
const handle = createInteractionHandler(interactionStream$, modules);
|
||||
|
||||
@@ -18,12 +17,10 @@ export function makeInteractionHandler([emitter,,, modules, client]: DependencyL
|
||||
handle(isCommand),
|
||||
handle(isModal),
|
||||
);
|
||||
return interactionHandler$
|
||||
.pipe(
|
||||
makeModuleExecutor(module => {
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
concatMap(payload => executeModule(emitter, payload)),
|
||||
);
|
||||
return interactionHandler$.pipe(
|
||||
makeModuleExecutor(module => {
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
concatMap(payload => executeModule(emitter, payload)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function fmt(msg: string, prefix: string): string[] {
|
||||
}
|
||||
|
||||
export function makeMessageHandler(
|
||||
[emitter,, log, modules, client]: DependencyList,
|
||||
[emitter, , log, modules, client]: DependencyList,
|
||||
defaultPrefix: string | undefined,
|
||||
) {
|
||||
if (!defaultPrefix) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AnyModule } from '../../core/types/modules';
|
||||
import * as assert from 'node:assert';
|
||||
|
||||
export function startReadyEvent(
|
||||
[sEmitter,,, moduleManager, client]: DependencyList,
|
||||
[sEmitter, , , moduleManager, client]: DependencyList,
|
||||
allPaths: ObservableInput<string>,
|
||||
) {
|
||||
const ready$ = fromEvent(client!, 'ready').pipe(take(1));
|
||||
@@ -19,7 +19,10 @@ export function startReadyEvent(
|
||||
buildModules<Processed<AnyModule>>(allPaths, sEmitter, moduleManager),
|
||||
callInitPlugins({
|
||||
onStop: module => {
|
||||
sEmitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
sEmitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
);
|
||||
},
|
||||
onNext: ({ module }) => {
|
||||
sEmitter.emit('module.register', SernEmitter.success(module));
|
||||
@@ -30,7 +33,7 @@ export function startReadyEvent(
|
||||
.subscribe(module => {
|
||||
const result = registerModule(moduleManager, module);
|
||||
if (result.err) {
|
||||
throw Error(SernError.InvalidModuleType + " " + result.val);
|
||||
throw Error(SernError.InvalidModuleType + ' ' + result.val);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -39,16 +42,14 @@ function registerModule<T extends Processed<AnyModule>>(
|
||||
manager: ModuleManager,
|
||||
module: T,
|
||||
): Result<void, void> {
|
||||
|
||||
const { id, fullPath } = manager.getMetadata(module);
|
||||
|
||||
assert.ok(module.type > 0 && module.type < 1<<10, `Found ${module.name} at ${fullPath}, which does not have a valid type`);
|
||||
if (module.type === CommandType.Both
|
||||
|| module.type === CommandType.Text
|
||||
) {
|
||||
assert.ok(
|
||||
module.type > 0 && module.type < 1 << 10,
|
||||
`Found ${module.name} at ${fullPath}, which does not have a valid type`,
|
||||
);
|
||||
if (module.type === CommandType.Both || module.type === CommandType.Text) {
|
||||
module.alias?.forEach(a => manager.set(`${a}_A0`, fullPath));
|
||||
}
|
||||
return Result.wrap(() => manager.set(id, fullPath));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,10 @@ import { Service, useContainerRaw } from '../../core/ioc';
|
||||
import { DependencyList, Processed } from '../types';
|
||||
import { Dependencies } from '../../core/ioc/types';
|
||||
|
||||
|
||||
|
||||
export function makeEventsHandler(
|
||||
[emitter, err, log, moduleManager, client]: DependencyList,
|
||||
allPaths: ObservableInput<string>,
|
||||
) {
|
||||
|
||||
//code smell
|
||||
const intoDispatcher = (e: Processed<EventModule | CommandModule>) => {
|
||||
switch (e.type) {
|
||||
@@ -37,7 +34,10 @@ export function makeEventsHandler(
|
||||
buildModules<Processed<EventModule>>(allPaths, emitter, moduleManager),
|
||||
callInitPlugins({
|
||||
onStop: module =>
|
||||
emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
|
||||
emitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
),
|
||||
onNext: ({ module }) => {
|
||||
emitter.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
|
||||
@@ -27,7 +27,7 @@ const appBitField = 0b000000011111;
|
||||
* This corresponds to an ApplicationCommandType or ComponentType
|
||||
* TextCommands are 0 as they aren't either or.
|
||||
*/
|
||||
function apiType(t: CommandType|EventType) {
|
||||
function apiType(t: CommandType | EventType) {
|
||||
if (t === CommandType.Both || t === CommandType.Modal) return 1;
|
||||
const log = Math.log2(t);
|
||||
return (appBitField & t) !== 0 ? log : log - 2;
|
||||
@@ -38,9 +38,7 @@ function apiType(t: CommandType|EventType) {
|
||||
* A is for any ApplicationCommand. C is for any ComponentCommand
|
||||
* Then, another number generated by apiType function is appended
|
||||
*/
|
||||
export function uniqueId(t: CommandType|EventType) {
|
||||
export function uniqueId(t: CommandType | EventType) {
|
||||
const am = (appBitField & t) !== 0 ? 'A' : 'C';
|
||||
return am + apiType(t);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { Wrapper } from '../shared';
|
||||
*/
|
||||
|
||||
export function init(wrapper: Wrapper) {
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const dependencies = useDependencies();
|
||||
@@ -32,50 +31,47 @@ export function init(wrapper: Wrapper) {
|
||||
const mode = debugModuleLoading(wrapper.mode ?? process.env.MODE);
|
||||
|
||||
if (wrapper.events !== undefined) {
|
||||
makeEventsHandler(
|
||||
dependencies,
|
||||
getFullPathTree(wrapper.events, mode),
|
||||
);
|
||||
makeEventsHandler(dependencies, getFullPathTree(wrapper.events, mode));
|
||||
}
|
||||
|
||||
startReadyEvent(
|
||||
dependencies,
|
||||
getFullPathTree(wrapper.commands, mode)
|
||||
).add(() => {
|
||||
startReadyEvent(dependencies, getFullPathTree(wrapper.commands, mode)).add(() => {
|
||||
const endTime = performance.now();
|
||||
logger?.info({ message: `sern: registered all modules in ${((endTime - startTime) / 1000).toFixed(2)} s` });
|
||||
logger?.info({
|
||||
message: `sern: registered all modules in ${((endTime - startTime) / 1000).toFixed(
|
||||
2,
|
||||
)} s`,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const messages$ = makeMessageHandler(dependencies, wrapper.defaultPrefix);
|
||||
const interactions$ = makeInteractionHandler(dependencies);
|
||||
|
||||
merge(
|
||||
messages$,
|
||||
interactions$
|
||||
).pipe(
|
||||
catchError(handleError(errorHandler, logger)),
|
||||
finalize(() => {
|
||||
logger?.info({ message: 'A stream closed or reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
merge(messages$, interactions$)
|
||||
.pipe(
|
||||
catchError(handleError(errorHandler, logger)),
|
||||
finalize(() => {
|
||||
logger?.info({ message: 'A stream closed or reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
function debugModuleLoading(mode: string|undefined) {
|
||||
console.info(`Detected mode: "${mode}"`)
|
||||
if(mode === undefined) {
|
||||
console.info("No mode found in process.env, assuming DEV");
|
||||
function debugModuleLoading(mode: string | undefined) {
|
||||
console.info(`Detected mode: "${mode}"`);
|
||||
if (mode === undefined) {
|
||||
console.info('No mode found in process.env, assuming DEV');
|
||||
}
|
||||
switch(mode) {
|
||||
case 'PROD': return false;
|
||||
switch (mode) {
|
||||
case 'PROD':
|
||||
return false;
|
||||
case 'DEV':
|
||||
case undefined: return true;
|
||||
case undefined:
|
||||
return true;
|
||||
default: {
|
||||
console.warn(mode + " is not a valid. Should be PROD or DEV");
|
||||
console.warn(mode + ' is not a valid. Should be PROD or DEV');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +83,7 @@ function useDependencies() {
|
||||
'@sern/errors',
|
||||
'@sern/logger',
|
||||
'@sern/modules',
|
||||
'@sern/client'
|
||||
'@sern/client',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@ export interface InitArgs<T extends Processed<Module>> {
|
||||
export interface ImportPayload<T> {
|
||||
module: T;
|
||||
absPath: string;
|
||||
[key: string]: unknown
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export * as Sern from './handler/sern';
|
||||
export * from './core';
|
||||
export { commandModule, eventModule, discordEvent, EventExecutable, CommandExecutable } from './handler/commands';
|
||||
export {
|
||||
commandModule,
|
||||
eventModule,
|
||||
discordEvent,
|
||||
EventExecutable,
|
||||
CommandExecutable,
|
||||
} from './handler/commands';
|
||||
export { controller } from './handler/sern';
|
||||
export type { Wrapper, Args } from './shared';
|
||||
|
||||
|
||||
@@ -24,21 +24,18 @@ export interface SernEventsMapping {
|
||||
warning: [Payload];
|
||||
}
|
||||
|
||||
|
||||
export type Awaitable<T> = PromiseLike<T> | T;
|
||||
|
||||
|
||||
export type Deprecated<Message extends string> = [never, Message];
|
||||
|
||||
|
||||
export interface Wrapper {
|
||||
commands: string;
|
||||
defaultPrefix?: string;
|
||||
events?: string;
|
||||
/**
|
||||
* Overload to enable mode in case developer does not use a .env file.
|
||||
*/
|
||||
mode?: 'DEV' | 'PROD'
|
||||
* Overload to enable mode in case developer does not use a .env file.
|
||||
*/
|
||||
mode?: 'DEV' | 'PROD';
|
||||
/*
|
||||
* @deprecated
|
||||
*/
|
||||
@@ -47,7 +44,6 @@ export interface Wrapper {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Thanks to @kelsny
|
||||
export type ParseType<T> = {
|
||||
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
|
||||
@@ -56,4 +52,3 @@ export type ParseType<T> = {
|
||||
export type Args = ParseType<{ text: string[]; slash: SlashOptions }>;
|
||||
|
||||
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
{
|
||||
"extends": "./tsconfig-esm.json"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const shared = {
|
||||
correctVarValueBeforeDeclaration: true, //need this to treeshake esm discord.js empty import
|
||||
annotations: true,
|
||||
},
|
||||
dts: false
|
||||
dts: false,
|
||||
};
|
||||
export default defineConfig([
|
||||
{
|
||||
@@ -50,12 +50,12 @@ export default defineConfig([
|
||||
await writeFile('./dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }));
|
||||
},
|
||||
...shared,
|
||||
},
|
||||
},
|
||||
{
|
||||
dts: {
|
||||
only: true
|
||||
only: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
outDir: 'dist'
|
||||
}
|
||||
outDir: 'dist',
|
||||
},
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user