Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/abstractiti #340

Merged
merged 25 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 0 additions & 450 deletions .dependency-cruiser.js

This file was deleted.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sern/handler",
"packageManager": "[email protected]",
"version": "3.1.1",
"version": "3.2.0",
"description": "A complete, customizable, typesafe, & reactive framework for discord bots.",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand All @@ -15,7 +15,6 @@
},
"scripts": {
"watch": "tsup --watch",
"clean-modules": "rimraf node_modules/ && npm install",
"lint": "eslint src/**/*.ts",
"format": "eslint src/**/*.ts --fix",
"build:dev": "tsup --metafile",
Expand Down Expand Up @@ -47,8 +46,7 @@
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "5.58.0",
"@typescript-eslint/parser": "5.59.1",
"dependency-cruiser": "^13.0.5",
"discord.js": "14.11.0",
"discord.js": "^14.11.0",
"esbuild": "^0.17.0",
"eslint": "8.39.0",
"prettier": "2.8.8",
Expand Down
6 changes: 5 additions & 1 deletion renovate.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"extends": ["config:base", "helpers:pinGitHubActionDigests", "group:allNonMajor"],
"extends": [
"config:base",
"helpers:pinGitHubActionDigests",
"group:allNonMajor"
],
"major": {
"dependencyDashboardApproval": true,
"reviewers": ["EvolutionX-10", "jacoobes", "Murtatrxx"]
Expand Down
11 changes: 4 additions & 7 deletions src/core/contracts/error-handling.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import type { CommandModule,Processed, EventModule } from "../../types/core-modules";

/**
* @since 2.0.0
*/
export interface ErrorHandling {
/**
* Number of times the process should throw an error until crashing and exiting
*/
keepAlive: number;

/**
* @deprecated
* Version 4 will remove this method
*/
crash(err: Error): never;
/**
* A function that is called on every crash. Updates keepAlive.
* If keepAlive is 0, the process crashes.
* A function that is called on every throw.
* @param error
*/
updateAlive(error: Error): void;

}
11 changes: 8 additions & 3 deletions src/core/contracts/module-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import type {
} from '../../types/core-modules';
import { CommandType } from '../structures';

interface MetadataAccess {
getMetadata(m: Module): CommandMeta | undefined;
setMetadata(m: Module, c: CommandMeta): void;
}

/**
* @since 2.0.0
* @internal - direct access to the module manager will be removed in version 4
*/
export interface ModuleManager {
export interface ModuleManager extends MetadataAccess {
get(id: string): string | undefined;
getMetadata(m: Module): CommandMeta | undefined;
setMetadata(m: Module, c: CommandMeta): void;

set(id: string, path: string): void;
getPublishableCommands(): Promise<CommandModule[]>;
getByNameCommandType<T extends CommandType>(
Expand Down
6 changes: 3 additions & 3 deletions src/core/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function partitionPlugins(
export function treeSearch(
iAutocomplete: AutocompleteInteraction,
options: SernOptionsData[] | undefined,
): SernAutocompleteData | undefined {
): SernAutocompleteData & { parent?: string } | undefined {
if (options === undefined) return undefined;
//clone to prevent mutation of original command module
const _options = options.map(a => ({ ...a }));
Expand Down Expand Up @@ -68,11 +68,11 @@ export function treeSearch(
const parentAndOptionMatches =
subcommands.has(parent) && cur.name === choice.name;
if (parentAndOptionMatches) {
return cur;
return { ...cur, parent };
}
} else {
if (cur.name === choice.name) {
return cur;
return { ...cur, parent: undefined };
}
}
}
Expand Down
86 changes: 77 additions & 9 deletions src/core/ioc/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as assert from 'assert';
import { composeRoot, useContainer } from './dependency-injection';
import type { DependencyConfiguration } from '../../types/ioc';
import { CoreContainer } from './container';

import { Result } from 'ts-results-es'
import { DefaultServices } from '../_internal';
import { AnyFunction } from '../../types/utility';
//SIDE EFFECT: GLOBAL DI
let containerSubject: CoreContainer<Partial<Dependencies>>;

Expand All @@ -20,17 +22,83 @@ export function useContainerRaw() {
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,
) {
const dependencyBuilder = (container: any, excluded: string[]) => {
type Insertable =
| ((container: CoreContainer<Dependencies>) => unknown )
| Record<PropertyKey, unknown>
return {
/**
* Insert a dependency into your container.
* Supply the correct key and dependency
*/
add(key: keyof Dependencies, v: Insertable) {
Result
.wrap(() => container.add({ [key]: v}))
.expect("Failed to add " + key);
},
/**
* Exclude any dependencies from being added.
* Warning: this could lead to bad errors if not used correctly
*/
exclude(...keys: (keyof Dependencies)[]) {
keys.forEach(key => excluded.push(key));
},
/**
* @param key the key of the dependency
* @param v The dependency to swap out.
* Swap out a preexisting dependency.
*/
swap(key: keyof Dependencies, v: Insertable) {
Result
.wrap(() => container.upsert({ [key]: v }))
.expect("Failed to update " + key);
},
/**
* @param key the key of the dependency
* @param cleanup Provide cleanup for the dependency at key. First parameter is the dependency itself
* @example
* ```ts
* addDisposer('dbConnection', (dbConnection) => dbConnection.end())
* ```
* Swap out a preexisting dependency.
*/
addDisposer(key: keyof Dependencies, cleanup: AnyFunction) {
Result
.wrap(() => container.addDisposer({ [key] : cleanup }))
.expect("Failed to addDisposer for" + key);
}
};
};

type CallbackBuilder = (c: ReturnType<typeof dependencyBuilder>) => any

type ValidDependencyConfig =
| CallbackBuilder
| DependencyConfiguration;

export const insertLogger = (containerSubject: CoreContainer<any>) => {
containerSubject
.upsert({'@sern/logger': () => new DefaultServices.DefaultLogging});
}
export async function makeDependencies<const T extends Dependencies>
(conf: ValidDependencyConfig) {
//Until there are more optional dependencies, just check if the logger exists
//SIDE EFFECT
containerSubject = new CoreContainer();
await composeRoot(containerSubject, conf);
if(typeof conf === 'function') {
const excluded: string[] = [];
conf(dependencyBuilder(containerSubject, excluded));
if(!excluded.includes('@sern/logger')) {
assert.ok(!containerSubject.getTokens()['@sern/logger'])
insertLogger(containerSubject);
}
containerSubject.ready();
} else {
composeRoot(containerSubject, conf);
}

return useContainer<T>();
}



14 changes: 4 additions & 10 deletions src/core/ioc/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
.subscribe({ complete: unsubscribe });

(this as Container<{}, {}>)
.add({
'@sern/errors': () => new DefaultServices.DefaultErrorHandling(),
'@sern/emitter': () => new SernEmitter(),
'@sern/store': () => new ModuleStore(),
})
.add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling(),
'@sern/emitter': () => new SernEmitter(),
'@sern/store': () => new ModuleStore() })
.add(ctx => {
return {
'@sern/modules': () =>
Expand All @@ -34,19 +32,15 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
});
}


isReady() {

return this.ready$.closed;
}
override async disposeAll() {

const otherDisposables = Object
.entries(this._context)
.flatMap(([key, value]) =>
'dispose' in value
? [key]
: []);
'dispose' in value ? [key] : []);

for(const key of otherDisposables) {
this.addDisposer({ [key]: (dep: Disposable) => dep.dispose() } as never);
Expand Down
9 changes: 3 additions & 6 deletions src/core/ioc/dependency-injection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { CoreDependencies, DependencyConfiguration, IntoDependencies } from '../../types/ioc';
import { DefaultServices } from '../_internal';
import { useContainerRaw } from './base';
import { insertLogger, useContainerRaw } from './base';
import { CoreContainer } from './container';

/**
Expand Down Expand Up @@ -53,16 +52,14 @@ export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]
* Finally, update the containerSubject with the new container state
* @param conf
*/
export async function composeRoot(
export function composeRoot(
container: CoreContainer<Partial<Dependencies>>,
conf: DependencyConfiguration,
) {
//container should have no client or logger yet.
const hasLogger = conf.exclude?.has('@sern/logger');
if (!hasLogger) {
container.upsert({
'@sern/logger': () => new DefaultServices.DefaultLogging(),
});
insertLogger(container);
}
//Build the container based on the callback provided by the user
conf.build(container as CoreContainer<Omit<CoreDependencies, '@sern/client'>>);
Expand Down
19 changes: 11 additions & 8 deletions src/core/module-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,21 @@ export type ModuleResult<T> = Promise<ImportPayload<T>>;
* export default commandModule({})
*/
export async function importModule<T>(absPath: string) {
let module = await import(absPath).then(esm => esm.default);
let fileModule = await import(absPath);

assert(module, `Found no export for module at ${absPath}. Forgot to ignore with "!"? (!${basename(absPath)})?`);
if ('default' in module) {
module = module.default;
let commandModule = fileModule.default;

assert(commandModule , `Found no export @ ${absPath}. Forgot to ignore with "!"? (!${basename(absPath)})?`);
if ('default' in commandModule ) {
commandModule = commandModule.default;
}
return Result
.wrap(() => module.getInstance())
.unwrapOr(module) as T;
.wrap(() => ({ module: commandModule.getInstance() }))
.unwrapOr({ module: commandModule }) as T;
}

export async function defaultModuleLoader<T extends Module>(absPath: string): ModuleResult<T> {
let module = await importModule<T>(absPath);
let { module } = await importModule<{ module: T }>(absPath);
assert(module, `Found an undefined module: ${absPath}`);
return { module, absPath };
}
Expand All @@ -51,7 +53,8 @@ export const fmtFileName = (fileName: string) => parse(fileName).name;
export function buildModuleStream<T extends Module>(
input: ObservableInput<string>,
): Observable<ImportPayload<T>> {
return from(input).pipe(mergeMap(defaultModuleLoader<T>));
return from(input)
.pipe(mergeMap(defaultModuleLoader<T>));
}

export const getFullPathTree = (dir: string) => readPaths(resolve(dir));
Expand Down
41 changes: 41 additions & 0 deletions src/core/structures/command-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ReplyOptions } from "../../types/utility";
import type { Logging } from "../contracts";

export interface Response {
type: 'fail' | 'continue';
body?: ReplyOptions;
log?: { type: keyof Logging; message: unknown }
}

export const of = () => {
const payload = {
type: 'fail',
body: undefined,
log : undefined
} as Record<PropertyKey, unknown>

return {
/**
* @param {'fail' | 'continue'} p a status to determine if the error will
* terminate your application or continue. Warning and
*/
status: (p: 'fail' | 'continue') => {
payload.type = p;
return payload;
},
/**
* @param {keyof Logging} type Determine to log to logger[type].
* @param {T} message the message to log
*
* Log this error with the logger.
*/
log: <T=string>(type: keyof Logging, message: T) => {
payload.log = { type, message };
return payload;
},
reply: (bodyContent: ReplyOptions) => {
payload.body = bodyContent;
return payload;
}
};
}
6 changes: 3 additions & 3 deletions src/core/structures/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import { CoreContext } from '../structures/core-context';
import { Result, Ok, Err } from 'ts-results-es';
import * as assert from 'assert';
import { ReplyOptions } from '../../types/utility';

type ReplyOptions = string | Omit<InteractionReplyOptions, 'fetchReply'> | MessageReplyOptions;

/**
* @since 1.0.0
Expand Down Expand Up @@ -103,9 +103,9 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
public async reply(content: ReplyOptions) {
return safeUnwrap(
this.ctx
.map(m => m.reply(content as string | MessageReplyOptions))
.map(m => m.reply(content as MessageReplyOptions))
.mapErr(i =>
i.reply(content as string | InteractionReplyOptions).then(() => i.fetchReply()),
i.reply(content as InteractionReplyOptions).then(() => i.fetchReply()),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions src/core/structures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './context';
export * from './sern-emitter';
export * from './services';
export * from './module-store';
export * as CommandError from './command-error';
Loading