feat: types organization and cleaning up code base

This commit is contained in:
Jacob Nguyen
2023-05-09 22:49:29 -05:00
parent cd1568ff69
commit 8a537d670b
39 changed files with 591 additions and 536 deletions

View File

@@ -19,17 +19,4 @@ export interface ErrorHandling {
*/
updateAlive(error: Error): void;
}
/**
* @since 2.0.0
*/
export class DefaultErrorHandling implements ErrorHandling {
keepAlive = 5;
crash(error: Error): never {
throw error;
}
updateAlive(_: Error) {
this.keepAlive--;
}
}

View File

@@ -1,3 +1,3 @@
export { type ErrorHandling, DefaultErrorHandling } from './error-handling';
export { type Logging, DefaultLogging } from './logging';
export { type ModuleManager, DefaultModuleManager } from './module-manager';
export { type ErrorHandling } from './error-handling';
export type { Logging, LogPayload } from './logging';
export { type ModuleManager } from './module-manager';

View File

@@ -1,4 +1,3 @@
import type { LogPayload } from '../../types/core';
/**
* @since 2.0.0
*/
@@ -8,24 +7,5 @@ export interface Logging<T = unknown> {
info(payload: LogPayload<T>): void;
debug(payload: LogPayload<T>): void;
}
/**
* @since 2.0.0
*/
export class DefaultLogging implements Logging {
private date = () => new Date();
debug(payload: LogPayload): void {
console.debug(`DEBUG: ${this.date().toISOString()} -> ${payload.message}`);
}
error(payload: LogPayload): void {
console.error(`ERROR: ${this.date().toISOString()} -> ${payload.message}`);
}
info(payload: LogPayload): void {
console.info(`INFO: ${this.date().toISOString()} -> ${payload.message}`);
}
warning(payload: LogPayload): void {
console.warn(`WARN: ${this.date().toISOString()} -> ${payload.message}`);
}
}
export type LogPayload<T = unknown> = { message: T };

View File

@@ -1,6 +1,5 @@
import { ModuleStore } from '../../types/core';
import { CommandModule } from '../../types/module';
import { importModule } from '../module-loading';
import { CommandModule } from "../types/modules";
/**
* @since 2.0.0
*/
@@ -10,30 +9,4 @@ export interface ModuleManager {
getPublishableCommands(): Promise<CommandModule[]>;
remove(id: string) : boolean
}
/**
* @since 2.0.0
*/
export class DefaultModuleManager implements ModuleManager {
constructor(private moduleStore: ModuleStore) {}
remove(id: string): boolean {
throw new Error('Method not implemented.');
}
get(id: string) {
return this.moduleStore.get(id);
}
set(id: string, path: string): void {
this.moduleStore.set(id, path);
}
//not tested
getPublishableCommands(): Promise<CommandModule[]> {
const entries = this.moduleStore.entries();
const publishable = 0b000000110;
return Promise.all(
Array.from(entries)
.filter(([id]) => (Number.parseInt(id.at(-1)!) & publishable) !== 0)
.map(([, path]) => importModule<CommandModule>(path)),
);
}
}

View File

@@ -1,5 +1,5 @@
import { CommandType, EventType, PluginType } from './structures';
import type { Plugin, PluginResult, EventArgs, CommandArgs } from '../types/plugin';
import type { Plugin, PluginResult, EventArgs, CommandArgs } from './types/plugins';
import type { ClientEvents } from 'discord.js';
export function makePlugin<V extends unknown[]>(

View File

@@ -1,152 +0,0 @@
import { Container } from 'iti';
import type { Dependencies, DependencyConfiguration, MapDeps, Wrapper } from '../types/core';
import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from './contracts';
import { SernEmitter } from './structures';
import { SernError } from './structures/errors';
import * as assert from 'node:assert'
import * as types from 'node:util/types'
import { Awaitable } from '../types/handler';
export let containerSubject: Container<{}, {}>;
const requiredDependencyKeys = ['@sern/emitter', '@sern/errors', '@sern/logger'] as const;
/**
* @__PURE__
* @since 2.0.0.
* use single if you want a singleton, or an object that is called once.
* @param cb
*/
export function single<T>(cb: () => T) {
return cb;
}
/**
* @__PURE__
* @since 2.0.0
* Following iti's singleton and transient implementation,
* use transient if you want a new dependency every time your container getter is called
* @param cb
*/
export function transient<T>(cb: () => () => T) {
return cb;
}
/**
* Given the user's conf, check for any excluded dependency keys.
* Then, call conf.build to get the rest of the users' dependencies.
* Finally, update the containerSubject with the new container state
* @param conf
*/
export async function composeRoot<T extends Dependencies>(conf: DependencyConfiguration<T>) {
//container should have no client or logger yet.
const excludeLogger = conf.exclude?.has('@sern/logger');
if (!excludeLogger) {
containerSubject.add({
'@sern/logger': () => new DefaultLogging(),
});
}
//Build the container based on the callback provided by the user
const updatedContainer = await conf.build(containerSubject as Container<Omit<Dependencies, '@sern/client'>, {}>);
try {
updatedContainer.get('@sern/client');
} catch {
throw new Error(SernError.MissingRequired + " No client was provided")
}
if (!excludeLogger) {
updatedContainer.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
}
export function useContainer<const T extends Dependencies>() {
console.warn(`Warning: using a container hook is not recommended. Could lead to many unwanted side effects`);
return <V extends (keyof T)[]>(...keys: [...V]) =>
keys.map(key => (containerSubject as Container<T, {}>).get(key)) as MapDeps<T, V>;
}
/**
* Returns the underlying data structure holding all dependencies.
* Exposes methods from iti
*/
export function useContainerRaw() {
assert.ok(
containerSubject && (containerSubject as CoreContainer).isReady(),
"Could not find container or container wasn't ready. Did you call makeDependencies?"
);
return containerSubject;
}
/**
* @since 2.0.0
* @param conf a configuration for creating your project dependencies
*/
export async function makeDependencies<const T extends Dependencies>(
conf: DependencyConfiguration<T>,
) {
containerSubject = new CoreContainer();
//Until there are more optional dependencies, just check if the logger exists
await composeRoot(conf);
(containerSubject as CoreContainer).ready();
return useContainer<T>();
}
export interface Init {
init() : Awaitable<unknown>
}
/**
* Provides all the defaults for sern to function properly.
* The only user provided dependency needs to be @sern/client
*/
class CoreContainer extends Container<Dependencies, {}> {
private _ready = false;
constructor() {
super();
(this as Container<{}, {}>)
.add({
'@sern/errors': () => new DefaultErrorHandling(),
'@sern/store': () => new Map<string, string>(),
'@sern/emitter': () => new SernEmitter()
})
.add(ctx => {
return { '@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']) };
})
}
async withInit<const Keys extends keyof Dependencies>(...keys: Keys[]) {
if(this.isReady()) {
throw Error("You cannot call this method after sern has started");
}
for await (const k of keys) {
const dep = this.get(k);
assert.ok(dep !== undefined);
if('init' in dep && typeof dep.init === 'function') {
types.isAsyncFunction(dep.init)
? await dep.init()
: dep.init()
} else {
throw Error(`called withInit with key ${k} but found nothing to init`)
}
}
return this;
}
isReady() {
return this._ready;
}
ready() {
this._ready = true;
}
}
/**
* A way for sern to grab only the necessary dependencies.
* Returns a function which allows for the user to call for more dependencies.
*/
export function makeFetcher<Dep extends Dependencies>(
containerConfig: Wrapper['containerConfig'],
) {
return <const Keys extends (keyof Dep)[]>(otherKeys: [...Keys]) =>
containerConfig.get(
...requiredDependencyKeys,
...(otherKeys as (keyof Dependencies)[]),
) as MapDeps<Dep, [...typeof requiredDependencyKeys, ...Keys]>;
}

View File

@@ -1,6 +1,6 @@
import { Err, Ok } from 'ts-results-es';
import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js';
import type { SernAutocompleteData, SernOptionsData } from '../types/module';
import type { SernAutocompleteData, SernOptionsData } from './types/modules';
//function wrappers for empty ok / err
export const ok = /* @__PURE__*/ () => Ok.EMPTY;

View File

@@ -1,4 +1,34 @@
export * from './contracts';
export * from './create-plugins';
export * from './structures';
export { single, transient, useContainerRaw, makeDependencies } from './dependencies';
export * from './ioc';
export type {
CommandModule,
EventModule,
BothCommand,
ContextMenuMsg,
ContextMenuUser,
SlashCommand,
TextCommand,
ButtonCommand,
StringSelectCommand,
MentionableSelectCommand,
UserSelectCommand,
ChannelSelectCommand,
RoleSelectCommand,
ModalSubmitCommand,
DiscordEventCommand,
SernEventCommand,
ExternalEventCommand,
CommandModuleDefs,
EventModuleDefs,
BaseOptions,
SernAutocompleteData
} from './types/modules';
export type {
Controller,
PluginResult,
InitPlugin,
ControlPlugin,
Plugin
} from './types/plugins';

39
src/core/ioc/base.ts Normal file
View File

@@ -0,0 +1,39 @@
import * as assert from "assert";
import { composeRoot, useContainer } from "./dependency-injection";
import { DependencyConfiguration, Dependencies } from "./types";
import { CoreContainer } from "../structures/container";
//SIDE EFFECT: GLOBAL DI
let containerSubject: CoreContainer<Partial<Dependencies>>;
/**
* Returns the underlying data structure holding all dependencies.
* Exposes methods from iti
*/
export function useContainerRaw() {
assert.ok(
containerSubject && containerSubject.isReady(),
"Could not find container or container wasn't ready. Did you call makeDependencies?"
);
return containerSubject;
}
/**
* @since 2.0.0
* @param conf a configuration for creating your project dependencies
*/
export async function makeDependencies<const T extends Dependencies>(
conf: DependencyConfiguration<T>,
) {
//Until there are more optional dependencies, just check if the logger exists
//SIDE EFFECT
containerSubject = new CoreContainer()
await composeRoot(conf);
//SIDE EFFECT
containerSubject.ready();
return useContainer<T>();
}

View File

@@ -0,0 +1,78 @@
import type { CoreDependencies, Dependencies, DependencyConfiguration, MapDeps, IntoDependencies } 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.
* Creates a singleton object.
* @param cb
*/
export function single<T>(cb: () => T) {
return cb;
}
/**
* @__PURE__
* @since 2.0.0
* Creates a transient object
* @param cb
*/
export function transient<T>(cb: () => () => T) {
return cb;
}
export function Service(key: string): unknown
export function Service<T extends keyof Dependencies>(key: T) {
return useContainerRaw().get(key)!
}
export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]) {
const container = useContainerRaw();
return keys.map(k => container.get(k)!) as IntoDependencies<T>
}
/**
* Given the user's conf, check for any excluded dependency keys.
* Then, call conf.build to get the rest of the users' dependencies.
* Finally, update the containerSubject with the new container state
* @param conf
*/
export async function composeRoot<T extends Dependencies>(conf: DependencyConfiguration<T>) {
//container should have no client or logger yet.
const excludeLogger = conf.exclude?.has('@sern/logger');
const container = useContainerRaw();
if (!excludeLogger) {
container.upsert({
'@sern/logger': () => new DefaultLogging(),
});
}
//Build the container based on the callback provided by the user
const updatedContainer = await conf.build(container as CoreContainer<CoreDependencies>);
try {
updatedContainer.get('@sern/client');
} catch {
throw new Error(SernError.MissingRequired + " No client was provided")
}
if (!excludeLogger) {
updatedContainer.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
}
export function useContainer<const T extends Dependencies>() {
console.warn(`
Warning: using a container hook 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
src/core/ioc/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { useContainerRaw, makeDependencies } from './base';
export { Service, Services, single, transient } from './dependency-injection';
export type { Singleton, Transient } from './types'

47
src/core/ioc/types.ts Normal file
View File

@@ -0,0 +1,47 @@
import { Container, UnpackFunction } from "iti";
import { Awaitable, ModuleStore } from "../../shared";
import { ErrorHandling, Logging, ModuleManager } from "../contracts";
import { SernEmitter } from "../";
import EventEmitter from "node:events";
export type Singleton<T> = () => T;
export type Transient<T> = () => () => T;
export interface CoreDependencies {
'@sern/logger'?: Singleton<Logging>;
'@sern/emitter': Singleton<SernEmitter>;
'@sern/store': Singleton<ModuleStore>;
'@sern/modules': Singleton<ModuleManager>;
'@sern/errors': Singleton<ErrorHandling>;
}
export interface Dependencies extends CoreDependencies {
'@sern/client': Singleton<EventEmitter>;
}
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
} & { length: Tuple['length'] };
export interface DependencyConfiguration<T extends Dependencies> {
//@deprecated. Loggers will always be included in the future
exclude?: Set<'@sern/logger'>;
build: (root: Container<CoreDependencies, {}>) => Awaitable<Container<T, {}>>;
}
//To be removed in future
//prettier-ignore
export type MapDeps<Deps extends Dependencies, T extends readonly unknown[]> = T extends [
infer First extends keyof Deps,
...infer Rest extends readonly unknown[],
]
? [
UnpackFunction<Deps[First]>,
...(MapDeps<Deps, Rest> extends [never] ? [] : MapDeps<Deps, Rest>),
]
: [never];

View File

@@ -1,14 +1,15 @@
import { SernError } from './structures/errors';
import { type Result, Err, Ok } from 'ts-results-es';
import { Processed } from '../types/core';
import { Module } from '../types/module';
import { Module } from './types/modules';
import * as assert from 'node:assert';
import util from 'node:util';
import { type Observable, from, mergeMap, ObservableInput } from 'rxjs';
import { readdir, stat } from 'fs/promises';
import { basename, join, resolve } from 'path';
import { Processed } from '../handler/types';
export type ModuleResult<T> = Promise<Result<Processed<T>, SernError>>;
export async function importModule<T>(absPath: string) {
/// #if MODE === 'esm'
return import(absPath).then(i => i.default as T);

View File

@@ -17,12 +17,12 @@ import {
share,
switchMap,
} from 'rxjs';
import type { PluginResult, VoidResult } from '../types/plugin';
import { Result } from 'ts-results-es';
import { Awaitable } from '../types/handler';
import { EventEmitter } from 'node:events';
import { ErrorHandling, Logging } from './contracts';
import util from 'node:util'
import { Awaitable } from '../shared';
import { PluginResult, VoidResult } from './types/plugins';
/**
* if {src} is true, mapTo V, else ignore
* @param item

View File

@@ -0,0 +1,45 @@
import { Container } from "iti";
import { DefaultErrorHandling, DefaultModuleManager, SernEmitter } from "../";
import { isAsyncFunction} from "node:util/types";
import * as assert from 'node:assert'
import { Dependencies } from "../ioc/types";
/**
* Provides all the defaults for sern to function properly.
* The only user provided dependency needs to be @sern/client
*/
export class CoreContainer<T extends Partial<Dependencies>> extends Container<T, {}> {
private _ready = false;
constructor() {
super();
(this as Container<{}, {}>)
.add({
'@sern/errors': () => new DefaultErrorHandling(),
'@sern/emitter': () => new SernEmitter(),
'@sern/modules': () => new DefaultModuleManager(new Map())
})
}
async withInit<const Keys extends keyof Dependencies>(...keys: Keys[]) {
if(this.isReady()) {
throw Error("You cannot call this method after sern has started");
}
for await (const k of keys) {
const dep = this.get(k);
assert.ok(dep !== undefined);
if('init' in dep && typeof dep.init === 'function') {
isAsyncFunction(dep.init)
? await dep.init()
: dep.init()
} else {
throw Error(`called withInit with key ${k} but found nothing to init`)
}
}
return this;
}
isReady() {
return this._ready;
}
ready() {
this._ready = true;
}
}

View File

@@ -10,8 +10,8 @@ import {
} from 'discord.js';
import { CoreContext } from './core-context';
import { Result, Ok, Err } from 'ts-results-es';
import { ReplyOptions } from '../../types/handler';
import * as assert from 'assert';
import { ReplyOptions } from '../../shared';
/**
* @since 1.0.0

View File

@@ -1,3 +1,4 @@
export * from './enums';
export * from './context';
export * from './sern-emitter';
export * from './services'

View File

@@ -1,7 +1,7 @@
import { EventEmitter } from 'node:events';
import type { Payload, SernEventsMapping } from '../../types/handler';
import { PayloadType } from '../../core/structures';
import type { Module } from '../../types/module';
import { Payload, SernEventsMapping } from '../../shared';
import { Module } from '../types/modules';
/**
* @since 1.0.0

View File

@@ -0,0 +1,14 @@
import { ErrorHandling } from "../../contracts";
/**
* @since 2.0.0
*/
export class DefaultErrorHandling implements ErrorHandling {
keepAlive = 5;
crash(error: Error): never {
throw error;
}
updateAlive(_: Error) {
this.keepAlive--;
}
}

View File

@@ -0,0 +1,3 @@
export * from './error-handling';
export * from './logger';
export * from './module-manager';

View File

@@ -0,0 +1,23 @@
import { LogPayload, Logging } from "../../contracts";
/**
* @since 2.0.0
*/
export class DefaultLogging implements Logging {
private date = () => new Date();
debug(payload: LogPayload): void {
console.debug(`DEBUG: ${this.date().toISOString()} -> ${payload.message}`);
}
error(payload: LogPayload): void {
console.error(`ERROR: ${this.date().toISOString()} -> ${payload.message}`);
}
info(payload: LogPayload): void {
console.info(`INFO: ${this.date().toISOString()} -> ${payload.message}`);
}
warning(payload: LogPayload): void {
console.warn(`WARN: ${this.date().toISOString()} -> ${payload.message}`);
}
}

View File

@@ -0,0 +1,32 @@
import { ModuleStore } from "../../../shared";
import { ModuleManager } from "../../contracts";
import { importModule } from "../../module-loading";
import { CommandModule } from "../../types/modules";
/**
* @since 2.0.0
*/
export class DefaultModuleManager implements ModuleManager {
constructor(private moduleStore: ModuleStore) {}
remove(id: string): boolean {
throw new Error('Method not implemented.');
}
get(id: string) {
return this.moduleStore.get(id);
}
set(id: string, path: string): void {
this.moduleStore.set(id, path);
}
//not tested
getPublishableCommands(): Promise<CommandModule[]> {
const entries = this.moduleStore.entries();
const publishable = 0b000000110;
return Promise.all(
Array.from(entries)
.filter(([id]) => (Number.parseInt(id.at(-1)!) & publishable) !== 0)
.map(([, path]) => importModule<CommandModule>(path)),
);
}
}

View File

@@ -23,22 +23,22 @@ import {
UserContextMenuCommandInteraction,
UserSelectMenuInteraction,
} from 'discord.js';
import { InitArgs } from './core';
import { Args, Payload, SlashOptions } from '../types/handler';
import { Context } from '../classic/context';
import { Processed } from '../types/core';
import { CommandType, PluginType } from '../core/structures/enums';
import type { Awaitable, SernEventsMapping } from './handler';
import type { InitPlugin, ControlPlugin } from './plugin';
import { EventType } from '../core/structures/enums';
import type { AnyCommandPlugin, AnyEventPlugin } from './plugin';
import { sernMeta } from '../commands';
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 {
fullPath: string;
id: string;
}
export type AnyDefinedModule = Processed<CommandModule | EventModule>;
export interface Module {
type: CommandType | EventType;
name?: string;
@@ -48,12 +48,7 @@ export interface Module {
[sernMeta]: CommandMeta;
execute: (...args: any[]) => Awaitable<any>;
}
export interface CommandTypeModule extends Module {
type: CommandType;
}
export interface EventTypeModule extends Module {
type: EventType;
}
export interface SernEventCommand<T extends keyof SernEventsMapping = keyof SernEventsMapping>
extends Module {
name?: T;
@@ -144,71 +139,7 @@ export interface BothCommand extends Module {
options?: SernOptionsData[];
execute: (ctx: Context, args: Args) => Awaitable<unknown>;
}
export interface CommandArgsMatrix {
[CommandType.Text]: {
[PluginType.Control]: [Context, ['text', string[]]];
[PluginType.Init]: [InitArgs<Processed<TextCommand>>];
};
[CommandType.Slash]: {
[PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]];
[PluginType.Init]: [InitArgs<Processed<SlashCommand>>];
};
[CommandType.Both]: {
[PluginType.Control]: [Context, Args];
[PluginType.Init]: [InitArgs<Processed<BothCommand>>];
};
[CommandType.CtxMsg]: {
[PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction];
[PluginType.Init]: [InitArgs<Processed<ContextMenuMsg>>];
};
[CommandType.CtxUser]: {
[PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction];
[PluginType.Init]: [InitArgs<Processed<ContextMenuUser>>];
};
[CommandType.Button]: {
[PluginType.Control]: [/* library coupled */ ButtonInteraction];
[PluginType.Init]: [InitArgs<Processed<ButtonCommand>>];
};
[CommandType.StringSelect]: {
[PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<StringSelectCommand>>];
};
[CommandType.RoleSelect]: {
[PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<RoleSelectCommand>>];
};
[CommandType.ChannelSelect]: {
[PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<ChannelSelectCommand>>];
};
[CommandType.MentionableSelect]: {
[PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<MentionableSelectCommand>>];
};
[CommandType.UserSelect]: {
[PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<UserSelectCommand>>];
};
[CommandType.Modal]: {
[PluginType.Control]: [/* library coupled */ ModalSubmitInteraction];
[PluginType.Init]: [InitArgs<Processed<ModalSubmitCommand>>];
};
}
export interface EventArgsMatrix {
[EventType.Discord]: {
[PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents];
[PluginType.Init]: [InitArgs<Processed<DiscordEventCommand>>];
};
[EventType.Sern]: {
[PluginType.Control]: [Payload];
[PluginType.Init]: [InitArgs<Processed<SernEventCommand>>];
};
[EventType.External]: {
[PluginType.Control]: unknown[];
[PluginType.Init]: [InitArgs<Processed<ExternalEventCommand>>];
};
}
export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand;
export type CommandModule =
| TextCommand

119
src/core/types/plugins.ts Normal file
View File

@@ -0,0 +1,119 @@
/*
* Plugins can be inserted on all commands and are emitted
*
* 1. On ready event, where all commands are loaded.
* 2. On corresponding observable (when command triggers)
*
* The goal of plugins is to organize commands and
* provide extensions to repetitive patterns
* examples include refreshing modules,
* categorizing commands, cool-downs, permissions, etc.
* Plugins are reminiscent of middleware in express.
*/
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 { 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';
export type PluginResult = Awaitable<VoidResult>;
export type VoidResult = Result<void, void>;
export interface Controller {
next: () => Ok<void>;
stop: () => Err<void>;
}
export interface Plugin<Args extends any[] = any[]> {
type: PluginType;
execute: (...args: Args) => PluginResult;
}
export interface InitPlugin<Args extends any[] = any[]> {
type: PluginType.Init;
execute: (...args: Args) => PluginResult;
}
export interface ControlPlugin<Args extends any[] = any[]> {
type: PluginType.Control;
execute: (...args: Args) => PluginResult;
}
export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<CommandModule>>]>;
export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<EventModule>>]>;
export type CommandArgs<
I extends CommandType = CommandType,
J extends PluginType = PluginType,
> = CommandArgsMatrix[I][J];
export type EventArgs<
I extends EventType = EventType,
J extends PluginType = PluginType,
> = EventArgsMatrix[I][J];
export interface CommandArgsMatrix {
[CommandType.Text]: {
[PluginType.Control]: [Context, ['text', string[]]];
[PluginType.Init]: [InitArgs<Processed<TextCommand>>];
};
[CommandType.Slash]: {
[PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]];
[PluginType.Init]: [InitArgs<Processed<SlashCommand>>];
};
[CommandType.Both]: {
[PluginType.Control]: [Context, Args];
[PluginType.Init]: [InitArgs<Processed<BothCommand>>];
};
[CommandType.CtxMsg]: {
[PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction];
[PluginType.Init]: [InitArgs<Processed<ContextMenuMsg>>];
};
[CommandType.CtxUser]: {
[PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction];
[PluginType.Init]: [InitArgs<Processed<ContextMenuUser>>];
};
[CommandType.Button]: {
[PluginType.Control]: [/* library coupled */ ButtonInteraction];
[PluginType.Init]: [InitArgs<Processed<ButtonCommand>>];
};
[CommandType.StringSelect]: {
[PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<StringSelectCommand>>];
};
[CommandType.RoleSelect]: {
[PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<RoleSelectCommand>>];
};
[CommandType.ChannelSelect]: {
[PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<ChannelSelectCommand>>];
};
[CommandType.MentionableSelect]: {
[PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<MentionableSelectCommand>>];
};
[CommandType.UserSelect]: {
[PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction];
[PluginType.Init]: [InitArgs<Processed<UserSelectCommand>>];
};
[CommandType.Modal]: {
[PluginType.Control]: [/* library coupled */ ModalSubmitInteraction];
[PluginType.Init]: [InitArgs<Processed<ModalSubmitCommand>>];
};
}
export interface EventArgsMatrix {
[EventType.Discord]: {
[PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents];
[PluginType.Init]: [InitArgs<Processed<DiscordEventCommand>>];
};
[EventType.Sern]: {
[PluginType.Control]: [Payload];
[PluginType.Init]: [InitArgs<Processed<SernEventCommand>>];
};
[EventType.External]: {
[PluginType.Control]: unknown[];
[PluginType.Init]: [InitArgs<Processed<ExternalEventCommand>>];
};
}

View File

@@ -1,10 +1,10 @@
import { ClientEvents } from 'discord.js';
import { CommandType, EventType, PluginType } from './core/structures';
import { AnyEventPlugin, Plugin } from './types/plugin';
import { CommandModule, EventModule, InputCommand, InputEvent } from './types/module';
import { partition } from './core/functions';
import { filename, filePath } from './core/module-loading';
import { Awaitable } from './types/handler';
import { CommandType, EventType, PluginType } from '../core/structures';
import { AnyEventPlugin, Plugin } from '../core/types/plugins';
import { CommandModule, EventModule, InputCommand, InputEvent } from '../core/types/modules';
import { partition } from '../core/functions';
import { filename, filePath } from '../core/module-loading';
import { Awaitable } from '../shared';
export const sernMeta = Symbol('@sern/meta');
const appBitField = 0b000000011111;
/*

View File

@@ -1,5 +1,3 @@
import type { Processed } from '../../types/core';
import type { BothCommand, CommandModule, EventModule, Module } from '../../types/module';
import { EventEmitter } from 'node:events';
import * as assert from 'node:assert';
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
@@ -8,9 +6,10 @@ import { createResultResolver } from './generic';
import { AutocompleteInteraction, BaseInteraction, Message } from 'discord.js';
import { treeSearch } from '../../core/functions';
import { SernError } from '../../core/structures/errors';
import { Args } from '../../types/handler';
import { CommandType, Context, EventType } from '../../core';
import { CommandType, Context } from '../../core';
import { isAutocomplete } from '../../core/predicates';
import { Args, Processed } from '../types';
import { BothCommand, CommandModule, Module } from '../../core/types/modules';
export function dispatchInteraction<
T extends CommandModule,
@@ -101,7 +100,7 @@ export function createDispatcher(payload: {
}) {
switch (payload.module.type) {
case CommandType.Text:
throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream.');
throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream. ' + payload.module);
case CommandType.Slash:
case CommandType.Both: {
if (isAutocomplete(payload.event)) {
@@ -109,7 +108,7 @@ export function createDispatcher(payload: {
* Autocomplete is a special case that
* must be handled separately, since it's
* too different from regular command modules
* cast safety: payload is already guaranteed to be a slash command or both command
* CAST SAFETY: payload is already guaranteed to be a slash command or both command
*/
return dispatchAutocomplete(payload as never);
}

View File

@@ -8,19 +8,18 @@ import { ModuleManager } from '../../core';
import { SernError } from '../../core/structures/errors';
import { callPlugin, everyPluginOk, filterMap, filterMapTo } from '../../core/operators';
import { defaultModuleLoader } from '../../core/module-loading';
import { ImportPayload, Processed } from '../../types/core';
import { CommandModule, Module } from '../../types/module';
import { CommandModule, Module, AnyModule } from '../../core/types/modules';
import { contextArgs, createDispatcher, dispatchMessage } from './dispatchers';
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 { AnyModule } from '../../types/module';
import { sernMeta } from '../commands';
import { Err, Result } from 'ts-results-es';
import { Awaitable } from '../../types/handler';
import { fmt } from './messages';
import { ControlPlugin, VoidResult } from '../../types/plugin';
import { ControlPlugin, VoidResult } from '../../core/types/plugins';
import { ImportPayload, Processed } from '../types';
import { Awaitable } from '../../shared';
function createGenericHandler<Source, Narrowed extends Source, Output>(
source: Observable<Source>,

View File

@@ -5,9 +5,10 @@ import { SernEmitter } from '../../core';
import { sharedObservable } from '../../core/operators';
import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates';
import { createInteractionHandler, executeModule, makeModuleExecutor } from './generic';
import { DependencyList } from '../../types/core';
import { DependencyList } from '../types';
export function makeInteractionHandler([emitter,,, modules, client]: DependencyList ) {
export function makeInteractionHandler([emitter, _, _1, modules, client]: DependencyList ) {
const interactionStream$ = sharedObservable<Interaction>(client, 'interactionCreate');
const handle = createInteractionHandler(interactionStream$, modules);
@@ -22,6 +23,6 @@ export function makeInteractionHandler([emitter, _, _1, modules, client]: Depend
makeModuleExecutor(module => {
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
}),
concatMap(module => executeModule(emitter, module)),
concatMap(payload => executeModule(emitter, payload)),
);
}

View File

@@ -4,7 +4,7 @@ import type { Message } from 'discord.js';
import { SernEmitter } from '../../core';
import { sharedObservable } from '../../core/operators';
import { createMessageHandler, executeModule, isNonBot, makeModuleExecutor } from './generic';
import { DependencyList } from '../../types/core';
import { DependencyList } from '../types';
/**
* Removes the first character(s) _[depending on prefix length]_ of the message
@@ -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) {

View File

@@ -3,15 +3,15 @@ import { CommandType } from '../../core/structures';
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/core';
import { Module } from '../../types/module';
import { SernEmitter } from '../../core';
import { sernMeta } from '../commands';
import { Processed, DependencyList } from '../types';
import * as assert from 'node:assert';
import { buildModules, callInitPlugins } from './generic';
import { Module } from '../../core/types/modules';
export function startReadyEvent(
[sEmitter, errorHandler, , moduleManager, client]: DependencyList,
[sEmitter,,, moduleManager, client]: DependencyList,
allPaths: ObservableInput<string>,
) {
const ready$ = fromEvent(client!, 'ready').pipe(take(1));
@@ -32,9 +32,9 @@ export function startReadyEvent(
}),
)
.subscribe(module => {
const result = registerModule(moduleManager, module as Processed<Module>);
const result = registerModule(moduleManager, module);
if (result.err) {
errorHandler.crash(Error(SernError.InvalidModuleType));
throw Error(SernError.InvalidModuleType);
}
});
}
@@ -44,7 +44,9 @@ function registerModule<T extends Processed<Module>>(
module: T,
): Result<void, void> {
const { id, fullPath } = module[sernMeta];
if (module.type === CommandType.Both || module.type === CommandType.Text) {
if (module.type === CommandType.Both
|| module.type === CommandType.Text
) {
assert.ok('alias' in module);
assert.ok(Array.isArray(module.alias));
module.alias?.forEach(a => manager.set(`${a}__A0`, fullPath));

View File

@@ -1,32 +1,30 @@
import { ObservableInput, catchError, finalize, map, mergeAll, of } from 'rxjs';
import type { Dependencies, Processed, Wrapper } from '../../types/core';
import type { CommandModule, EventModule } from '../../types/module';
import type { EventEmitter } from 'node:events';
import { SernEmitter } from '../../core';
import type { ErrorHandling, Logging } from '../../core/contracts';
import { EventType } from '../../core/structures';
import { SernError } from '../../core/structures/errors';
import { eventDispatcher } from './dispatchers';
import { handleError } from '../../core/contracts/error-handling';
import { useContainerRaw } from '../../core/dependencies';
import { buildModules, callInitPlugins } from './generic';
import { handleError } from '../../core/operators';
import { Service, useContainerRaw } from '../../core/ioc';
import { DependencyList, Processed } from '../types';
export function makeEventsHandler(
[s, err, log, client]: [SernEmitter, ErrorHandling, Logging | undefined, EventEmitter],
[emitter, err, log,, client]: DependencyList,
allPaths: ObservableInput<string>,
containerGetter: Wrapper['containerConfig'],
) {
const lazy = (k: string) => containerGetter.get(k as keyof Dependencies)[0];
//code smell
const intoDispatcher = (e: Processed<EventModule | CommandModule>) => {
switch (e.type) {
case EventType.Sern:
return eventDispatcher(e, s);
return eventDispatcher(e, emitter);
case EventType.Discord:
return eventDispatcher(e, client);
case EventType.External:
return eventDispatcher(e, lazy(e.emitter));
return eventDispatcher(e, Service(e.emitter));
default:
return err.crash(
Error(SernError.InvalidModuleType + ' while creating event handler'),
@@ -35,12 +33,12 @@ export function makeEventsHandler(
};
of(null)
.pipe(
buildModules(allPaths, s),
buildModules(allPaths, emitter),
callInitPlugins({
onStop: module =>
s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
onNext: ({ module }) => {
s.emit('module.register', SernEmitter.success(module));
emitter.emit('module.register', SernEmitter.success(module));
return module;
},
}),

View File

@@ -2,12 +2,13 @@ import { makeEventsHandler } from './events/user-defined';
import { makeInteractionHandler } from './events/interactions';
import { startReadyEvent } from './events/ready';
import { makeMessageHandler } from './events/messages';
import { makeFetcher, makeDependencies, useContainerRaw } from '../core/dependencies';
import { err, ok } from '../core/functions';
import { Wrapper } from '../types/core';
import { getCommands } from '../core/module-loading';
import { getFullPathTree } from '../core/module-loading';
import { catchError, finalize, merge } from 'rxjs';
import { handleError } from '../core/contracts/error-handling';
import { handleError } from '../core/operators';
import { Services, useContainerRaw } from '../core/ioc';
import { Wrapper } from '../shared';
/**
* @since 1.0.0
* @param wrapper Options to pass into sern.
@@ -23,20 +24,20 @@ import { handleError } from '../core/contracts/error-handling';
* })
* ```
*/
export function init(wrapper: Wrapper) {
const startTime = performance.now();
const dependenciesAnd = makeFetcher(wrapper.containerConfig);
const dependencies = dependenciesAnd(['@sern/modules', '@sern/client']);
const dependencies = useDependencies();
if (wrapper.events !== undefined) {
makeEventsHandler(
dependenciesAnd(['@sern/client']),
wrapper.events,
wrapper.containerConfig,
dependencies,
getFullPathTree(wrapper.events),
);
}
startReadyEvent(dependencies, getCommands(wrapper.commands)).add(() => console.log('ready'));
startReadyEvent(dependencies, getFullPathTree(wrapper.commands)).add(() => console.log('ready'));
const logger = dependencies[2];
const errorHandler = dependencies[1];
@@ -50,27 +51,28 @@ export function init(wrapper: Wrapper) {
).pipe(
catchError(handleError(errorHandler, logger)),
finalize(() => {
logger?.info({ message: 'a stream closed or reached end of lifetime' });
logger?.info({ message: 'A stream closed or reached end of lifetime' });
useContainerRaw()
?.disposeAll()
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
})
).subscribe()
).subscribe();
const endTime = performance.now();
dependencies[2]?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
}
function useDependencies() {
return Services(
'@sern/emitter',
'@sern/errors',
'@sern/logger',
'@sern/modules',
'@sern/client'
);
}
/**
* @deprecated - Please import the function directly:
* ```ts
* import { makeDependencies } from '@sern/handler'
*
* ```
*/
export { makeDependencies };
/**
* @since 1.0.0
* The object passed into every plugin to control a command's behavior

23
src/handler/types.ts Normal file
View File

@@ -0,0 +1,23 @@
import { ErrorHandling, Logging, ModuleManager, SernEmitter } from "../core";
import EventEmitter from "node:events";
import { Module } from "../core/types/modules";
export type Processed<T> = T & { name: string; description: string };
export type DependencyList = [
SernEmitter,
ErrorHandling,
Logging | undefined,
ModuleManager,
EventEmitter,
];
export interface InitArgs<T extends Processed<Module>> {
module: T;
absPath: string;
}
export interface ImportPayload<T> {
module: T;
absPath: string;
}

View File

@@ -1,9 +1,5 @@
export * as Sern from './handler/sern';
export * from './types/core';
export * from './types/handler';
export * from './types/module';
export * from './types/plugin';
export * from './core';
export { commandModule, eventModule, discordEvent } from './handler/commands'
export { controller } from './handler/sern';
export { commandModule, eventModule, discordEvent } from './commands';

View File

@@ -1,29 +1,17 @@
import type {
CommandInteractionOptionResolver,
InteractionReplyOptions,
MessageReplyOptions,
CommandInteractionOptionResolver,
} from 'discord.js';
import { Processed } from './core';
import { AnyModule, CommandModule, EventModule } from './module';
import { PayloadType } from '../core';
export type Awaitable<T> = PromiseLike<T> | T;
// Thanks to @kelsny
export type ParseType<T> = {
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
}[keyof T];
export type Args = ParseType<{ text: string[]; slash: SlashOptions }>;
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;
import { PayloadType } from './core';
import { Dependencies } from './core/ioc/types';
import { AnyModule } from './core/types/modules';
export type ReplyOptions =
| string
| Omit<InteractionReplyOptions, 'fetchReply'>
| MessageReplyOptions;
export type AnyDefinedModule = Processed<CommandModule | EventModule>;
export type Payload =
| { type: PayloadType.Success; module: AnyModule }
| { type: PayloadType.Failure; module?: AnyModule; reason: string | Error }
@@ -35,3 +23,31 @@ export interface SernEventsMapping {
error: [Payload];
warning: [Payload];
}
export type Awaitable<T> = PromiseLike<T> | T;
export type ModuleStore = Map<string, string>;
export type Deprecated<Message extends string> = [never, Message];
export interface Wrapper {
commands: string;
defaultPrefix?: string;
events?: string;
containerConfig?: {
get: (...keys: (keyof Dependencies)[]) => unknown[];
};
}
// Thanks to @kelsny
export type ParseType<T> = {
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
}[keyof T];
export type Args = ParseType<{ text: string[]; slash: SlashOptions }>;
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;

View File

@@ -1,80 +0,0 @@
/**
* Core typings.
* Includes iti, dependencies, and other commonly used types
* Should not have discord.js imports
*/
import { type EventEmitter } from 'node:events';
import { ErrorHandling, Logging, ModuleManager, SernEmitter } from '../core';
import { Container, UnpackFunction } from 'iti';
import { Module } from './module';
import { Awaitable } from './handler';
export type ModuleStore = Map<string, string>;
export type DependencyList = [
SernEmitter,
ErrorHandling,
Logging | undefined,
ModuleManager,
EventEmitter,
];
/**
* After modules are transformed, name and description are given default values if none
* are provided to Module. This type represents that transformation
*/
export type LogPayload<T = unknown> = { message: T };
export type Singleton<T> = () => T;
export type Transient<T> = () => () => T;
export interface CoreDependencies {
'@sern/logger'?: Singleton<Logging>;
'@sern/emitter': Singleton<SernEmitter>;
'@sern/store': Singleton<ModuleStore>;
'@sern/modules': Singleton<ModuleManager>;
'@sern/errors': Singleton<ErrorHandling>;
}
export interface Dependencies extends CoreDependencies {
'@sern/client': Singleton<EventEmitter>;
}
//prettier-ignore
export type MapDeps<Deps extends Dependencies, T extends readonly unknown[]> = T extends [
infer First extends keyof Deps,
...infer Rest extends readonly unknown[],
]
? [
UnpackFunction<Deps[First]>,
...(MapDeps<Deps, Rest> extends [never] ? [] : MapDeps<Deps, Rest>),
]
: [never];
/*
* @deprecated
* Will remove optional logger in the future
*/export type OptionalDependencies = '@sern/logger';
export type Processed<T> = T & { name: string; description: string };
export type Deprecated<Message extends string> = [never, Message];
export interface DependencyConfiguration<T extends Dependencies> {
//@deprecated. Loggers will always be included in the future
exclude?: Set<OptionalDependencies>;
build: (root: Container<CoreDependencies, {}>) => Awaitable<Container<T, {}>>;
}
export interface ImportPayload<T> {
module: T;
absPath: string;
}
export interface Wrapper {
commands: string;
defaultPrefix?: string;
events?: string;
containerConfig: {
get: (...keys: (keyof Dependencies)[]) => unknown[];
};
}
export interface InitArgs<T extends Processed<Module>> {
module: T;
absPath: string;
}

View File

@@ -1,52 +0,0 @@
/*
* Plugins can be inserted on all commands and are emitted
*
* 1. On ready event, where all commands are loaded.
* 2. On corresponding observable (when command triggers)
*
* The goal of plugins is to organize commands and
* provide extensions to repetitive patterns
* examples include refreshing modules,
* categorizing commands, cool-downs, permissions, etc.
* Plugins are reminiscent of middleware in express.
*/
import type { Err, Ok, Result } from 'ts-results-es';
import type { CommandType, EventType, PluginType } from '../core/structures';
import type { CommandArgsMatrix, CommandModule, EventArgsMatrix, EventModule } from './module';
import type { InitArgs } from './core';
import type { Awaitable } from './handler';
import { Processed } from './core';
export type PluginResult = Awaitable<VoidResult>;
export type VoidResult = Result<void, void>;
export interface Controller {
next: () => Ok<void>;
stop: () => Err<void>;
}
export interface Plugin<Args extends any[] = any[]> {
type: PluginType;
execute: (...args: Args) => PluginResult;
}
export interface InitPlugin<Args extends any[] = any[]> {
type: PluginType.Init;
execute: (...args: Args) => PluginResult;
}
export interface ControlPlugin<Args extends any[] = any[]> {
type: PluginType.Control;
execute: (...args: Args) => PluginResult;
}
export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<CommandModule>>]>;
export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<EventModule>>]>;
export type CommandArgs<
I extends CommandType = CommandType,
J extends PluginType = PluginType,
> = CommandArgsMatrix[I][J];
export type EventArgs<
I extends EventType = EventType,
J extends PluginType = PluginType,
> = EventArgsMatrix[I][J];

View File

@@ -19,7 +19,8 @@ export default defineConfig([
target: 'node16',
tsconfig: './tsconfig-esm.json',
outDir: './dist/esm',
splitting: false,
splitting: true,
bundle: true,
esbuildPlugins: [ifdefPlugin({ variables: { MODE: 'esm' }, verbose: true })],
outExtension() {
return {
@@ -50,5 +51,9 @@ export default defineConfig([
},
...shared,
},
// {
// dts: true,
// entry: ['src/presets/*.ts' ]
//
// }
]);

View File

@@ -5,13 +5,6 @@ __metadata:
version: 6
cacheKey: 8
"@cloudflare/workers-types@npm:^4.20230419.0":
version: 4.20230419.0
resolution: "@cloudflare/workers-types@npm:4.20230419.0"
checksum: db1eb1e74029da8ee3eaed8c8b83e1bc3a10801d328ccfddf39258e5eb679ffb34776fab441f6ac66aa304bc3715dedce77083aed3d79b4825639fb24970c164
languageName: node
linkType: hard
"@discordjs/builders@npm:^1.6.3":
version: 1.6.3
resolution: "@discordjs/builders@npm:1.6.3"
@@ -449,7 +442,6 @@ __metadata:
version: 0.0.0-use.local
resolution: "@sern/handler@workspace:."
dependencies:
"@cloudflare/workers-types": ^4.20230419.0
"@types/node": ^18.15.11
"@typescript-eslint/eslint-plugin": 5.58.0
"@typescript-eslint/parser": 5.58.0