chore: progress of globalizing dependencies type

This commit is contained in:
Jacob Nguyen
2023-05-10 21:57:55 -05:00
parent 57dfa09a73
commit 5e878cc97b
11 changed files with 49 additions and 82 deletions

View File

@@ -23,7 +23,8 @@ export type {
CommandModuleDefs,
EventModuleDefs,
BaseOptions,
SernAutocompleteData
SernAutocompleteData,
SernOptionsData
} from './types/modules';
export type {
Controller,

View File

@@ -1,14 +1,13 @@
import { SernError } from './structures/errors';
import { type Result, Err, Ok } from 'ts-results-es';
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>>;
import { ImportPayload } from '../handler/types';
import * as assert from 'node:assert'
import { sernMeta } from '../handler/commands';
export type ModuleResult<T> = Promise<Result<ImportPayload<T>, SernError>>;
export async function importModule<T>(absPath: string) {
/// #if MODE === 'esm'
@@ -18,19 +17,17 @@ export async function importModule<T>(absPath: string) {
/// #endif
}
export async function defaultModuleLoader<T extends Module>(absPath: string): ModuleResult<T> {
// prettier-ignore
const module = await importModule<T>(absPath);
if (module === undefined) {
return Err(SernError.UndefinedModule);
}
checkIsProcessed(module);
return Ok(module);
assert.ok(module.type > 0 && module.type < 1<<10, "Found a module that does not have a valid type");
assert.ok(module[sernMeta], "Found a module that isn't marked with sernMeta");
return Ok({ module, absPath });
}
function checkIsProcessed<T extends Module>(m: T): asserts m is Processed<T> {
assert.ok(m.name !== undefined, `name is not defined for ${util.format(m)}`);
assert.ok(m.description !== undefined, `description is not defined for ${util.format(m)}`);
}
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
@@ -42,7 +39,7 @@ export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
*/
export function buildModuleStream<T extends Module>(
input: ObservableInput<string>,
): Observable<Result<Processed<T>, SernError>> {
): Observable<Result<ImportPayload<T>, SernError>> {
return from(input).pipe(mergeMap(defaultModuleLoader<T>));
}

View File

@@ -1,32 +1,12 @@
import { ClientEvents } from 'discord.js';
import { CommandType, EventType, PluginType } from '../core/structures';
import { 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;
/*
* Generates a number based on CommandType.
* This corresponds to an ApplicationCommandType or ComponentType
* TextCommands are 0 as they aren't either or.
*/
function apiType(t: CommandType) {
if (t === CommandType.Both || t === CommandType.Modal) return 1;
const log = Math.log2(t);
return (appBitField & t) !== 0 ? log : log - 2;
}
/*
* Generates an id based on CommandType.
* A is for any ApplicationCommand. C is for any ComponentCommand
* Then, another number generated by apiType function is appended
*/
function uniqueId(t: CommandType) {
const am = (appBitField & t) !== 0 ? 'A' : 'C';
return am + apiType(t);
}
export const UNREGISTERED = "meow meow meow";
export const EMPTY_PATH = "purr purr purr";
/**
* @since 1.0.0 The wrapper function to define command modules for sern
* @param mod
@@ -36,18 +16,14 @@ export function commandModule(mod: InputCommand): CommandModule {
mod.plugins ?? [],
el => (el as Plugin).type === PluginType.Control,
);
const fullPath = filePath();
const name = mod.name ?? filename(fullPath);
return {
...mod,
description: mod.description ?? '...',
name,
onEvent,
plugins,
[sernMeta]: {
id: `${name}__${uniqueId(mod.type)}`,
fullPath,
},
id: UNREGISTERED,
fullPath: EMPTY_PATH
}
} as CommandModule;
}
/**
@@ -60,14 +36,12 @@ export function eventModule(mod: InputEvent): EventModule {
mod.plugins ?? [],
el => (el as Plugin).type === PluginType.Control,
);
const fullPath = filePath();
return {
name: mod.name ?? filename(fullPath),
onEvent,
plugins,
[sernMeta]: {
id: 'no-id',
fullPath,
id: UNREGISTERED,
fullPath: EMPTY_PATH
},
...mod,
} as EventModule;

View File

@@ -3,8 +3,8 @@ import {
InteractionType,
Message,
} from 'discord.js';
import { EMPTY, Observable, concatMap, filter, from, map, of, throwError, tap } from 'rxjs';
import { ModuleManager } from '../../core';
import { EMPTY, Observable, concatMap, filter, from, of, throwError, tap, MonoTypeOperatorFunction } from 'rxjs';
import { CommandType, EventType, ModuleManager } from '../../core';
import { SernError } from '../../core/structures/errors';
import { callPlugin, everyPluginOk, filterMap, filterMapTo } from '../../core/operators';
import { defaultModuleLoader } from '../../core/module-loading';
@@ -20,6 +20,7 @@ import { fmt } from './messages';
import { ControlPlugin, VoidResult } from '../../core/types/plugins';
import { ImportPayload, Processed } from '../types';
import { Awaitable } from '../../shared';
import { createId, uniqueId } from '../id';
function createGenericHandler<Source, Narrowed extends Source, Output>(
source: Observable<Source>,
@@ -44,8 +45,8 @@ export function createInteractionHandler<T extends Interaction>(
const fullPath = mg.get(createId(event as unknown as Interaction));
if (!fullPath)
return Err(SernError.UndefinedModule + ' No full path found in module store');
return defaultModuleLoader<CommandModule>(fullPath).then(res =>
res.map(module => createDispatcher({ module, event })),
return defaultModuleLoader<Processed<CommandModule>>(fullPath).then(res =>
res.map(payload => createDispatcher({ module: payload.module, event })),
);
},
);
@@ -58,39 +59,32 @@ export function createMessageHandler(
) {
return createGenericHandler(source, event => {
const [prefix, ...rest] = fmt(event.content, defaultPrefix);
const fullPath = mg.get(`${prefix}__A0`);
const fullPath = mg.get(`${prefix}_A0`);
if (fullPath === undefined) {
return Err(SernError.UndefinedModule + ' No full path found in module store');
}
return defaultModuleLoader<CommandModule>(fullPath)
return defaultModuleLoader<Processed<CommandModule>>(fullPath)
.then(result => {
const args = contextArgs(event, rest);
return result.map(module => dispatchMessage(module, args))
return result.map(payload => dispatchMessage(payload.module, args))
})
});
}
/**
* Creates a unique ID for a given interaction object.
* @param event The interaction object for which to create an ID.
* @returns A unique string ID based on the type and properties of the interaction object.
*/
function createId<T extends Interaction>(event: T) {
switch (event.type) {
case InteractionType.MessageComponent: {
return `${event.customId}__C${event.componentType}`;
}
case InteractionType.ApplicationCommand:
case InteractionType.ApplicationCommandAutocomplete: {
return `${event.commandName}__A${event.commandType}`;
}
case InteractionType.ModalSubmit: {
return `${event.customId}__C1`;
}
* IMPURE SIDE EFFECT
* This function assigns remaining, incomplete data to each imported module.
*/
function assignDefaults<T extends Module>(): MonoTypeOperatorFunction<ImportPayload<T>> {
return tap(
({ module, absPath }) => {
module.name ??= Files.filename(absPath);
module.description ??= "...";
module[sernMeta].fullPath = absPath;
module[sernMeta].id = `${module.name}_${uniqueId(module.type)}`
}
)
}
export function buildModules<T extends AnyModule>(
input: ObservableInput<string>,
sernEmitter: SernEmitter,
@@ -100,7 +94,7 @@ export function buildModules<T extends AnyModule>(
errTap(error => {
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
}),
map(module => ({ module, absPath: module[sernMeta].fullPath })),
assignDefaults<T>()
);
}
@@ -184,7 +178,7 @@ export function createResultResolver<
* ignore the module
*/
export function callInitPlugins<
T extends Processed<Module>,
T extends Processed<AnyModule>,
Args extends ImportPayload<T>,
>(config: { onStop?: (module: T) => unknown; onNext: (module: Args) => T }) {
return concatMap(

View File

@@ -26,3 +26,4 @@ export function makeInteractionHandler([emitter,,, modules, client]: DependencyL
concatMap(payload => executeModule(emitter, payload)),
);
}

View File

@@ -16,7 +16,7 @@ export function startReadyEvent(
const ready$ = fromEvent(client!, 'ready').pipe(take(1));
return ready$
.pipe(
buildModules(allPaths, sEmitter),
buildModules<Processed<AnyModule>>(allPaths, sEmitter),
callInitPlugins({
onStop: module => {
sEmitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure));
@@ -43,7 +43,7 @@ function registerModule<T extends Processed<AnyModule>>(
if (module.type === CommandType.Both
|| module.type === CommandType.Text
) {
module.alias?.forEach(a => manager.set(`${a}__A0`, fullPath));
module.alias?.forEach(a => manager.set(`${a}_A0`, fullPath));
}
return Result.wrap(() => manager.set(id, fullPath));
}

View File

@@ -33,7 +33,7 @@ export function makeEventsHandler(
};
of(null)
.pipe(
buildModules(allPaths, emitter),
buildModules<Processed<EventModule>>(allPaths, emitter),
callInitPlugins({
onStop: module =>
emitter.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),

View File

@@ -20,4 +20,5 @@ export interface InitArgs<T extends Processed<Module>> {
export interface ImportPayload<T> {
module: T;
absPath: string;
[key: string]: unknown
}

View File

@@ -2,4 +2,5 @@ export * as Sern from './handler/sern';
export * from './core';
export { commandModule, eventModule, discordEvent } from './handler/commands'
export { controller } from './handler/sern';
export type { Wrapper, Args } from './shared'

View File

@@ -4,7 +4,6 @@ import type {
MessageReplyOptions,
} from 'discord.js';
import { PayloadType } from './core';
import { Dependencies } from './core/ioc/types';
import { AnyModule } from './core/types/modules';
export type ReplyOptions =
@@ -27,7 +26,6 @@ export interface SernEventsMapping {
export type Awaitable<T> = PromiseLike<T> | T;
export type ModuleStore = Map<string, string>;
export type Deprecated<Message extends string> = [never, Message];

View File

@@ -54,9 +54,9 @@ export default defineConfig([
},
{
dts: {
only: true
only: true,
entry: 'src/index.ts'
},
entry: ['src/index.ts'],
outDir: 'dist'
}
]);