-file,-updateModule,publish?

This commit is contained in:
Jacob Nguyen
2024-06-08 00:20:24 -05:00
parent 45665292ae
commit bf071b7af4
11 changed files with 78 additions and 87 deletions

View File

@@ -44,8 +44,8 @@ const TypeMap = new Map<number, number>([[CommandType.Text, 0],
[CommandType.CtxUser, ApplicationCommandType.User],
[CommandType.CtxMsg, ApplicationCommandType.Message],
[CommandType.Button, ComponentType.Button],
[CommandType.Modal, InteractionType.ModalSubmit],
[CommandType.StringSelect, ComponentType.StringSelect],
[CommandType.Modal, InteractionType.ModalSubmit],
[CommandType.UserSelect, ComponentType.UserSelect],
[CommandType.MentionableSelect, ComponentType.MentionableSelect],
[CommandType.RoleSelect, ComponentType.RoleSelect],

View File

@@ -1,52 +0,0 @@
/**
* This file holds sern's rxjs operators used for processing data.
* Each function should be modular and testable, not bound to discord / sern
* and independent of each other.
*/
import {
concatMap,
EMPTY,
fromEvent,
Observable,
of,
OperatorFunction,
share,
} from 'rxjs';
import type { Emitter, ErrorHandling, Logging } from './interfaces';
import util from 'node:util';
import type { Result } from 'ts-results-es';
/**
* if {src} is true, mapTo V, else ignore
* @param item
*/
export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
return concatMap(keep => keep ? of(item()) : EMPTY);
}
export const arrayifySource = <T>(src: T) =>
Array.isArray(src) ? src : [src];
export const sharedEventStream = <T>(e: Emitter, eventName: string) =>
(fromEvent(e, eventName) as Observable<T>).pipe(share());
export function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) {
return (pload: unknown, caught: Observable<C>) => {
// This is done to fit the ErrorHandling contract
if(!emitter.emit('error', pload)) {
const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true }));
logging?.error({ message: util.inspect(pload) });
crashHandler.updateAlive(err);
}
return caught;
};
}
//// Temporary until i get rxjs operators working on ts-results-es
export const filterTap = <K, R>(onErr: (e: R) => void): OperatorFunction<Result<K, R>, K> =>
concatMap(result => {
if(result.isOk()) {
return of(result.value)
}
onErr(result.error);
return EMPTY;
})

View File

@@ -61,4 +61,5 @@ export const Presence = {
export type PresenceConfig <T extends (keyof Dependencies)[]> = {
inject?: [...T]
execute: (...v: IntoDependencies<T>) => PresenceResult;
};

View File

@@ -13,6 +13,7 @@ import { Result, Ok, Err } from 'ts-results-es';
import * as assert from 'assert';
import type { ReplyOptions } from '../../types/utility';
import { fmt } from '../functions'
import { SernError } from './enums';
/**
@@ -21,7 +22,7 @@ import { fmt } from '../functions'
* Message and ChatInputCommandInteraction
*/
export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
get options() {
if(this.isMessage()) {
const [, ...rest] = fmt(this.message.content, this.prefix);
@@ -30,6 +31,7 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
return this.interaction.options;
}
}
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>,
private __prefix?: string) {
@@ -94,6 +96,15 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
.mapErr(i => i.member));
}
get message(): Message {
return this.ctx.expect(SernError.MismatchEvent);
}
get interaction(): ChatInputCommandInteraction {
return this.ctx.expectErr(SernError.MismatchEvent);
}
public get client(): Client {
return safeUnwrap(this.ctx
.map(m => m.client)

View File

@@ -1,5 +1,4 @@
import { Result as Either } from 'ts-results-es';
import { SernError } from './enums';
import * as assert from 'node:assert';
/**
@@ -9,13 +8,6 @@ export abstract class CoreContext<M, I> {
protected constructor(protected ctx: Either<M, I>) {
assert.ok(typeof ctx === 'object' && ctx != null, "Context was nonobject or null");
}
get message(): M {
return this.ctx.expect(SernError.MismatchEvent);
}
get interaction(): I {
return this.ctx.expectErr(SernError.MismatchEvent);
}
public isMessage(): this is CoreContext<M, never> {
return this.ctx.isOk();
}

View File

@@ -1,11 +1,12 @@
import type { Interaction, Message, BaseInteraction } from 'discord.js';
import util from 'node:util';
import {
EMPTY, type Observable, concatMap, filter,
throwError, fromEvent, map, type OperatorFunction,
catchError, finalize, pipe, from, take,
catchError, finalize, pipe, from, take, share, of,
} from 'rxjs';
import * as Id from '../core/id'
import type { Emitter } from '../core/interfaces';
import type { Emitter, ErrorHandling, Logging } from '../core/interfaces';
import { SernError } from '../core/structures/enums'
import { Err, Ok, Result } from 'ts-results-es';
import type { UnpackedDependencies } from '../types/utility';
@@ -15,9 +16,22 @@ import { Context } from '../core/structures/context';
import { CommandType } from '../core/structures/enums'
import { inspect } from 'node:util'
import { disposeAll } from '../core/ioc/base';
import { arrayifySource, handleError } from '../core/operators';
import { resultPayload, isAutocomplete, treeSearch, fmt } from '../core/functions'
function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) {
return (pload: unknown, caught: Observable<C>) => {
// This is done to fit the ErrorHandling contract
if(!emitter.emit('error', pload)) {
const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true }));
logging?.error({ message: util.inspect(pload) });
crashHandler.updateAlive(err);
}
return caught;
};
}
const arrayify= <T>(src: T) =>
Array.isArray(src) ? src : [src];
interface ExecutePayload {
module: Module;
args: unknown[];
@@ -26,8 +40,20 @@ interface ExecutePayload {
[key: string]: unknown
}
export const filterTap = <K, R>(onErr: (e: R) => void): OperatorFunction<Result<K, R>, K> =>
concatMap(result => {
if(result.isOk()) {
return of(result.value)
}
onErr(result.error);
return EMPTY;
})
export const sharedEventStream = <T>(e: Emitter, eventName: string) =>
(fromEvent(e, eventName) as Observable<T>).pipe(share());
function intoPayload(module: Module, deps: Dependencies) {
return pipe(map(arrayifySource),
return pipe(map(arrayify),
map(args => ({ module, args, deps })),
map(p => p.args));
}
@@ -151,10 +177,7 @@ export function createMessageHandler(
* @param module the module that will be executed with task
* @param task the deferred execution which will be called
*/
export function executeModule(
emitter: Emitter,
{ module, args }: ExecutePayload,
) {
export function executeModule(emitter: Emitter, { module, args }: ExecutePayload) {
return from(Result.wrapAsync(async () => module.execute(...args)))
.pipe(concatMap(result => {
if (result.isOk()) {
@@ -181,6 +204,7 @@ export function createResultResolver<Output>(config: {
const { onStop, onNext } = config;
return async (payload: ExecutePayload) => {
const task = await callPlugins(payload);
if (!task) throw Error("Plugin did not return anything.");
if(task.isOk()) {
return onNext(payload, task.value) as Output;
} else {
@@ -189,18 +213,16 @@ export function createResultResolver<Output>(config: {
};
};
export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean ) {
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, absPath: _module.meta.absPath,
updateModule: (partial: Partial<Module>) => {
_module = { ..._module, ...partial };
return _module;
},
module: _module,
absPath: _module.meta.absPath,
deps
});
if (!res) throw Error("Plugin did not return anything.");
if(res.isErr()) {
if(emit) {
emitter?.emit('module.register',

View File

@@ -1,7 +1,6 @@
import type { Interaction } from 'discord.js';
import { mergeMap, merge, concatMap, EMPTY } from 'rxjs';
import { filterTap, sharedEventStream } from '../core/operators'
import { createInteractionHandler, executeModule, intoTask } from './event-utils';
import { createInteractionHandler, executeModule, intoTask, sharedEventStream, filterTap } from './event-utils';
import { SernError } from '../core/structures/enums'
import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload } from '../core/functions'
import { UnpackedDependencies } from '../types/utility';

View File

@@ -1,9 +1,8 @@
import { EMPTY, mergeMap, concatMap } from 'rxjs';
import type { Message } from 'discord.js';
import { createMessageHandler, executeModule, intoTask } from './event-utils';
import { PayloadType, SernError } from '../core/structures/enums'
import { createMessageHandler, executeModule, intoTask, sharedEventStream, filterTap} from './event-utils';
import { SernError } from '../core/structures/enums'
import { resultPayload } from '../core/functions'
import { filterTap, sharedEventStream } from '../core/operators'
import { UnpackedDependencies } from '../types/utility';
import type { Emitter } from '../core/interfaces';
@@ -36,7 +35,7 @@ function (deps: UnpackedDependencies, defaultPrefix?: string) {
const msgCommands$ = handle(isNonBot(defaultPrefix));
return msgCommands$.pipe(
filterTap(e => emitter.emit('warning', resultPayload(PayloadType.Warning, undefined, e))),
filterTap(e => emitter.emit('warning', resultPayload('warning', undefined, e))),
concatMap(intoTask(module => {
const result = resultPayload('failure', module, SernError.PluginFailure);
emitter.emit('module.activate', result);

View File

@@ -10,6 +10,7 @@ import { handleCrash } from './handlers/event-utils';
import { useContainerRaw } from './core/ioc/global';
import { UnpackedDependencies } from './types/utility';
import type { PresenceResult } from './core/presences';
import fs from 'fs/promises'
interface Wrapper {
commands: string;
@@ -63,3 +64,16 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
// listening to the message stream and interaction stream
merge(messages$, interactions$).pipe(handleCrash(deps)).subscribe();
}
export async function publisher() {
const directoryToWatch = "./src/commands";
const watcher = fs.watch(directoryToWatch, { recursive: true }, );
for await (const { eventType, filename } of watcher) {
switch(eventType) {
case 'change': {
console.log('change', filename)
} break;
}
}
}

View File

@@ -14,6 +14,7 @@ import type {
StringSelectMenuInteraction,
UserContextMenuCommandInteraction,
UserSelectMenuInteraction,
ChatInputCommandInteraction,
} from 'discord.js';
import type { CommandType, EventType } from '../core/structures/enums';
import { Context } from '../core/structures/context'
@@ -123,14 +124,14 @@ export interface DiscordEventCommand<T extends keyof ClientEvents = keyof Client
}
export interface TextCommand extends Module {
type: CommandType.Text;
execute: (ctx: Context, tbd: SDT) => Awaitable<unknown>;
execute: (ctx: Context & { get options(): string[] }, tbd: SDT) => Awaitable<unknown>;
}
export interface SlashCommand extends Module {
type: CommandType.Slash;
description: string;
options?: SernOptionsData[];
execute: (ctx: Context, tbd: SDT) => Awaitable<unknown>;
execute: (ctx: Context & { get options(): ChatInputCommandInteraction['options']}, tbd: SDT) => Awaitable<unknown>;
}
export interface BothCommand extends Module {

View File

@@ -56,9 +56,11 @@ vi.mock('discord.js', async (importOriginal) => {
});
function createRandomPlugin (s: 'go', mut?: Partial<Module>) {
return CommandInitPlugin(({ module, updateModule }) => {
return CommandInitPlugin(({ module }) => {
if(mut) {
updateModule(mut)
for(const [k,v] of Object.entries(mut)) {
module[k] = v
}
}
return s == 'go'
? controller.next()
@@ -105,8 +107,10 @@ test ('mutate with 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)
expect(s.name).not.equal(mod.name)
console.log(s)
expect("abc").equal(s.name)
})