From 61e82fdc7b15249c8eb90331d0df8f90514e7491 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:07:44 -0500 Subject: [PATCH] refactor: remove ts-results-es (#366) * remove tsresultses * remove test since it uses external api * opt in for simpler * add more debug information Signed-off-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> * add more debug information Signed-off-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> * clean up if else --------- Signed-off-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> --- package.json | 3 +- src/core/plugin.ts | 2 +- src/core/structures/context.ts | 92 +++++++++++++---------------- src/core/structures/core-context.ts | 18 ------ src/core/structures/result.ts | 20 +++++++ src/handlers/event-utils.ts | 24 ++++---- src/types/core-plugin.ts | 2 +- src/types/utility.ts | 2 +- test/handlers.test.ts | 7 --- yarn.lock | 8 --- 10 files changed, 77 insertions(+), 101 deletions(-) delete mode 100644 src/core/structures/core-context.ts create mode 100644 src/core/structures/result.ts diff --git a/package.json b/package.json index a8eb9673..a51954a9 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,7 @@ "callsites": "^3.1.0", "cron": "^3.1.7", "deepmerge": "^4.3.1", - "rxjs": "^7.8.0", - "ts-results-es": "^4.1.0" + "rxjs": "^7.8.0" }, "devDependencies": { "@faker-js/faker": "^8.0.1", diff --git a/src/core/plugin.ts b/src/core/plugin.ts index 65c49cf9..f2e39114 100644 --- a/src/core/plugin.ts +++ b/src/core/plugin.ts @@ -1,6 +1,6 @@ import { CommandType, PluginType } from './structures/enums'; import type { Plugin, PluginResult, CommandArgs, InitArgs } from '../types/core-plugin'; -import { Err, Ok } from 'ts-results-es'; +import { Err, Ok } from './structures/result'; export function makePlugin( type: PluginType, diff --git a/src/core/structures/context.ts b/src/core/structures/context.ts index 69b460cb..fb0a3751 100644 --- a/src/core/structures/context.ts +++ b/src/core/structures/context.ts @@ -8,8 +8,7 @@ import type { Snowflake, User, } from 'discord.js'; -import { CoreContext } from '../structures/core-context'; -import { Result, Ok, Err } from 'ts-results-es'; +import { Result, Ok, Err, val } from './result'; import * as assert from 'assert'; import type { ReplyOptions } from '../../types/utility'; import { fmt } from '../functions' @@ -21,39 +20,32 @@ import { SernError } from './enums'; * Provides values shared between * Message and ChatInputCommandInteraction */ -export class Context extends CoreContext { +export class Context { get options() { if(this.isMessage()) { const [, ...rest] = fmt(this.message.content, this.prefix); return rest; - } else { - return this.interaction.options; - } + } + return this.interaction.options; } protected constructor(protected ctx: Result, - private __prefix?: string) { - super(ctx); - } + private __prefix?: string) { } public get prefix() { return this.__prefix; } public get id(): Snowflake { - return safeUnwrap(this.ctx - .map(m => m.id) - .mapErr(i => i.id)); + return val(this.ctx).id } public get channel() { - return safeUnwrap(this.ctx.map(m => m.channel).mapErr(i => i.channel)); + return val(this.ctx).channel; } public get channelId(): Snowflake { - return safeUnwrap(this.ctx - .map(m => m.channelId) - .mapErr(i => i.channelId)); + return val(this.ctx).channelId; } /** @@ -61,9 +53,11 @@ export class Context extends CoreContext { * else, interaction.user */ public get user(): User { - return safeUnwrap(this.ctx - .map(m => m.author) - .mapErr(i => i.user)); + if(this.ctx.ok) { + return this.ctx.value.author; + } + return this.ctx.error.user; + } public get userId(): Snowflake { @@ -71,59 +65,60 @@ export class Context extends CoreContext { } public get createdTimestamp(): number { - return safeUnwrap(this.ctx - .map(m => m.createdTimestamp) - .mapErr(i => i.createdTimestamp)); + return val(this.ctx).createdTimestamp; } public get guild() { - return safeUnwrap(this.ctx - .map(m => m.guild) - .mapErr(i => i.guild)); + return val(this.ctx).guild; } public get guildId() { - return safeUnwrap(this.ctx - .map(m => m.guildId) - .mapErr(i => i.guildId)); + return val(this.ctx).guildId; } /* * interactions can return APIGuildMember if the guild it is emitted from is not cached */ public get member() { - return safeUnwrap(this.ctx - .map(m => m.member) - .mapErr(i => i.member)); + return val(this.ctx).member; } get message(): Message { - return this.ctx.expect(SernError.MismatchEvent); + if(this.ctx.ok) { + return this.ctx.value; + } + throw Error(SernError.MismatchEvent); + } + public isMessage(): this is Context & { ctx: Result } { + return this.ctx.ok; + } + + public isSlash(): this is Context & { ctx: Result } { + return !this.isMessage(); } get interaction(): ChatInputCommandInteraction { - return this.ctx.expectErr(SernError.MismatchEvent); + if(!this.ctx.ok) { + return this.ctx.error; + } + throw Error(SernError.MismatchEvent); } public get client(): Client { - return safeUnwrap(this.ctx - .map(m => m.client) - .mapErr(i => i.client)); + return val(this.ctx).client; } public get inGuild(): boolean { - return safeUnwrap(this.ctx - .map(m => m.inGuild()) - .mapErr(i => i.inGuild())); + return val(this.ctx).inGuild() } public async reply(content: ReplyOptions) { - return safeUnwrap( - this.ctx - .map(m => m.reply(content as MessageReplyOptions)) - .mapErr(i => - i.reply(content as InteractionReplyOptions).then(() => i.fetchReply())), - ); + if(this.ctx.ok) { + return this.ctx.value.reply(content as MessageReplyOptions) + } + interface FetchReply { fetchReply: true }; + return this.ctx.error.reply(content as InteractionReplyOptions & FetchReply) + } static wrap(wrappable: BaseInteraction | Message, prefix?: string): Context { @@ -134,10 +129,3 @@ export class Context extends CoreContext { return new Context(Err(wrappable), prefix); } } - -function safeUnwrap(res: Result) { - if(res.isOk()) { - return res.expect("Tried unwrapping message field: " + res) - } - return res.expectErr("Tried unwrapping interaction field" + res) -} diff --git a/src/core/structures/core-context.ts b/src/core/structures/core-context.ts deleted file mode 100644 index 73a92d58..00000000 --- a/src/core/structures/core-context.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Result as Either } from 'ts-results-es'; -import * as assert from 'node:assert'; - -/** - * @since 3.0.0 - */ -export abstract class CoreContext { - protected constructor(protected ctx: Either) { - assert.ok(typeof ctx === 'object' && ctx != null, "Context was nonobject or null"); - } - public isMessage(): this is CoreContext { - return this.ctx.isOk(); - } - - public isSlash(): this is CoreContext { - return !this.isMessage(); - } -} diff --git a/src/core/structures/result.ts b/src/core/structures/result.ts new file mode 100644 index 00000000..06d579f4 --- /dev/null +++ b/src/core/structures/result.ts @@ -0,0 +1,20 @@ +export type Result = + | { ok: true; value: Ok } + | { ok: false; error: Err }; + +export const Ok = (value: Ok) => ({ ok: true, value } as const); +export const Err = (error: Err) => ({ ok: false, error } as const); + +export const val = (r: Result) => r.ok ? r.value : r.error; +export const EMPTY_ERR = Err(undefined); + +/** + * Wrap an async operation that may throw an Error (`try-catch` style) into checked exception style + * @param op The operation function + */ +export async function wrapAsync(op: () => Promise): Promise> { + try { return op() + .then(Ok) + .catch(Err); } + catch (e) { return Promise.resolve(Err(e as E)); } +} diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index fbe25659..2ae15f0c 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -8,7 +8,7 @@ import { import * as Id from '../core/id' import type { Emitter, ErrorHandling, Logging } from '../core/interfaces'; import { SernError } from '../core/structures/enums' -import { Err, Ok, Result } from 'ts-results-es'; +import { EMPTY_ERR, Err, Ok, Result, wrapAsync } from '../core/structures/result'; import type { UnpackedDependencies } from '../types/utility'; import type { CommandModule, Module, Processed } from '../types/core-modules'; import * as assert from 'node:assert'; @@ -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) { @@ -43,7 +44,7 @@ interface ExecutePayload { export const filterTap = (onErr: (e: R) => void): OperatorFunction, K> => concatMap(result => { - if(result.isOk()) { + if(result.ok){ return of(result.value) } onErr(result.error); @@ -142,7 +143,7 @@ export function createInteractionHandler( .map(({ id, params }) => ({ module: mg.get(id), params })) .filter(({ module }) => module !== undefined); if(modules.length == 0) { - return Err.EMPTY; + return EMPTY_ERR; } const [{module, params}] = modules; return Ok(createDispatcher({ @@ -179,9 +180,9 @@ export function createMessageHandler( * @param task the deferred execution which will be called */ export function executeModule(emitter: Emitter, { module, args }: ExecutePayload) { - return from(Result.wrapAsync(async () => module.execute(...args))) + return from(wrapAsync(async () => module.execute(...args))) .pipe(concatMap(result => { - if (result.isOk()) { + if (result.ok){ emitter.emit('module.activate', resultPayload('success', module)); return EMPTY; } @@ -206,10 +207,10 @@ export function createResultResolver(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 { + if(!task.ok) { onStop?.(payload.module, String(task.error)); + } else { + return onNext(payload, task.value) as Output; } }; }; @@ -225,12 +226,13 @@ export async function callInitPlugins(_module: Module, deps: Dependencies, emit? 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(!result.ok) { if(emit) { emitter?.emit('module.register', resultPayload('failure', module, result.error ?? SernError.PluginFailure)); } - throw Error(result.error ?? SernError.PluginFailure); + throw Error((result.error ?? SernError.PluginFailure) + + 'on module ' + module.name + " " + module.meta.absPath); } } return module @@ -240,7 +242,7 @@ export async function callPlugins({ args, module, deps, params }: ExecutePayload let state = {}; for(const plugin of module.onEvent??[]) { const result = await plugin.execute(...args, { state, deps, params, type: module.type }); - if(result.isErr()) { + if(!result.ok) { return result; } if(isObject(result.value)) { diff --git a/src/types/core-plugin.ts b/src/types/core-plugin.ts index b63e23b9..9e1f6312 100644 --- a/src/types/core-plugin.ts +++ b/src/types/core-plugin.ts @@ -11,7 +11,6 @@ * Plugins are reminiscent of middleware in express. */ -import type { Result } from 'ts-results-es'; import type { Module, Processed, @@ -31,6 +30,7 @@ import type { UserContextMenuCommandInteraction, UserSelectMenuInteraction, } from 'discord.js'; +import { Result } from '../core/structures/result'; export type PluginResult = Awaitable|undefined, string|undefined>>; export interface InitArgs = Processed> { diff --git a/src/types/utility.ts b/src/types/utility.ts index dcbe3b22..50264fba 100644 --- a/src/types/utility.ts +++ b/src/types/utility.ts @@ -1,6 +1,6 @@ import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { Module } from './core-modules'; -import type { Result } from 'ts-results-es'; +import type { Result } from '../core/structures/result'; export type Awaitable = PromiseLike | T; diff --git a/test/handlers.test.ts b/test/handlers.test.ts index e066ec97..80c64114 100644 --- a/test/handlers.test.ts +++ b/test/handlers.test.ts @@ -120,13 +120,6 @@ test('init plugins replace array', async () => { expect(['a']).deep.equal(s.opts) }) -test('call control plugin ', async () => { - const plugin = CommandControlPlugin((ctx,sdt) => { - return controller.next(); - }); - const res = await plugin.execute(new ChatInputCommandInteraction(), {}) - expect(res.isOk()).toBe(true) -}) test('form sdt', async () => { diff --git a/yarn.lock b/yarn.lock index 177077c4..67271e48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -557,7 +557,6 @@ __metadata: discord.js: ^14.15.3 eslint: 8.39.0 rxjs: ^7.8.0 - ts-results-es: ^4.1.0 typescript: 5.0.2 vitest: ^1.6.0 languageName: unknown @@ -2959,13 +2958,6 @@ __metadata: languageName: node linkType: hard -"ts-results-es@npm:^4.1.0": - version: 4.2.0 - resolution: "ts-results-es@npm:4.2.0" - checksum: ff475c2f6d44377e0204211e6eafdbcabddf3ad09d40540ad5dee3d817eefbd48c07a21f5ad86864ef82cd8a5542a266af9dd8dd4d58d4766fdd6e79370519bb - languageName: node - linkType: hard - "tslib@npm:2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2"