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!: simpler plugins #193

Merged
merged 61 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2610305
feat: experimental plugin changes
jacoobes Jan 7, 2023
e56d38a
more refactors and name changes
jacoobes Jan 8, 2023
9c7ddbc
feat: update name usage and update dispatchers.ts
jacoobes Jan 8, 2023
549305d
fix:naming
jacoobes Jan 8, 2023
15b411c
feat: slightly safer typings than any[]
jacoobes Jan 8, 2023
0c56cac
fix: forgot to destructure arguments
jacoobes Jan 8, 2023
e00bd5e
feat: add special function
jacoobes Jan 8, 2023
d6b11e0
fix: typings
jacoobes Jan 8, 2023
b4d7609
feat: SUPER SIMPLIFY!!!
jacoobes Jan 9, 2023
9227d78
refactor: move promisifiedPlugins closer to call site
jacoobes Jan 9, 2023
eb39dc0
refactor: typings
jacoobes Jan 9, 2023
eaee691
refactor: typings
jacoobes Jan 9, 2023
a780508
refactor: consolidate resolving initplugins into one function
jacoobes Jan 9, 2023
2e568e7
refactor: better types
jacoobes Jan 9, 2023
74a5726
revert: remove unneeded function
jacoobes Jan 10, 2023
7f22946
revert: remove unneeded function
jacoobes Jan 10, 2023
e642a49
feat: dispatch work, simplify
jacoobes Jan 10, 2023
079554e
feat: move some observableHandling function to operators for clarity
jacoobes Jan 10, 2023
ea49b69
feat: simplify and document
jacoobes Jan 10, 2023
1748fb1
feat: simplifying sern and docs
jacoobes Jan 10, 2023
ab4bdaa
fix: typings
jacoobes Jan 11, 2023
724e9ae
docs: clarity of function name
jacoobes Jan 11, 2023
4939ac5
docs: add documentation for executeModule
jacoobes Jan 11, 2023
c2c7ba9
feat: contextArgs overloads
jacoobes Jan 11, 2023
600bc80
docs: found out why
jacoobes Jan 11, 2023
9d7760a
fix: typings
jacoobes Jan 11, 2023
46b9918
feat: shorten operators signature
jacoobes Jan 12, 2023
2c43771
refactor: switch to correct convention
jacoobes Jan 12, 2023
01ee934
refactor: take(1) -> first()
jacoobes Jan 12, 2023
9418bc6
refactor: revert
jacoobes Jan 12, 2023
aeee6ba
refactor: safer typings (less any) and more accurate typings
jacoobes Jan 12, 2023
53aee3b
style: prettier and short type aliases
jacoobes Jan 12, 2023
19bf2d4
fix: typings
jacoobes Jan 12, 2023
8cd812e
fix: typings
jacoobes Jan 12, 2023
a552a0f
docs: add deprecations
jacoobes Jan 12, 2023
e001c45
Merge branch 'main' into feat/simple-plugins
jacoobes Jan 12, 2023
1352f45
refactor: organization and moving stuff
jacoobes Jan 12, 2023
edaca54
pretty: prettey
jacoobes Jan 12, 2023
5cdc300
docs: describe file
jacoobes Jan 12, 2023
7dfc22b
chore: update dependencies and version
jacoobes Jan 12, 2023
404a8c7
docs: fix link for docasaurus
jacoobes Jan 13, 2023
e5a0aac
refactor: using a more appropriate operator function for closing an o…
jacoobes Jan 13, 2023
a92efde
fix!: changing single and many
jacoobes Jan 15, 2023
c14c62a
refactor: typings and simplifying composeRoot
jacoobes Jan 15, 2023
c34477d
fix: re-add logger into handleError
jacoobes Jan 15, 2023
a82a914
docs: comment
jacoobes Jan 15, 2023
c51568c
docs: new section
jacoobes Jan 15, 2023
d46e3c5
feat: help mitigate breaking changes
jacoobes Jan 15, 2023
d79c085
feat: help mitigate breaking changes
jacoobes Jan 15, 2023
c3e55a7
feat: help mitigate breaking changes and function overloads
jacoobes Jan 15, 2023
98f4978
feat: deprecate instead of remove
jacoobes Jan 15, 2023
45edb98
feat: partial remove and deprecate old symbols
jacoobes Jan 15, 2023
a11c5e4
revert: trying to accommodate old plugins is too difficult
jacoobes Jan 15, 2023
32678ca
docs: add many as deprecated
jacoobes Jan 15, 2023
9e8b17a
docs: update
jacoobes Jan 15, 2023
83b0042
feat: partial backwards compatability
jacoobes Jan 16, 2023
443ac09
refactor: renaming, docs, and exports more clean
jacoobes Jan 17, 2023
b60cc96
refactor: context got a lot simpler
jacoobes Jan 17, 2023
a6ac00f
refactor: imports
jacoobes Jan 17, 2023
b641472
docs: explain methods
jacoobes Jan 17, 2023
e2093ff
Merge remote-tracking branch 'origin/main' into feat/simple-plugins
jacoobes Jan 26, 2023
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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ yarn add @sern/handler
```sh
pnpm add @sern/handler
```

## Why?
- Most handlers don't support discord.js 14.7+
- Customizable commands
- Plug and play or customize to your liking
- Embraces reactive programming for consistent and reliable backend
- Customizable logger, error handling, and more
- Active development and growing [community](https://sern.dev/discord)
## 👀 Quick Look

* Support for discord.js v14 and all interactions
Expand Down Expand Up @@ -105,7 +111,7 @@ It is **highly encouraged** to use the [command line interface](https://github.c

## 👋 Contribute

- Read our contribution [guidelines](./.github/CONTRIBUTING.md) carefully
- Read our contribution [guidelines](https://github.com/sern-handler/handler/blob/main/.github/CONTRIBUTING.md) carefully
- Pull up on [issues](https://github.com/sern-handler/handler/issues) and report bugs
- All kinds of contributions are welcomed.

Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sern/handler",
"packageManager": "pnpm@7.25.0",
"version": "2.1.1",
"version": "2.5.0",
"description": "A customizable, batteries-included, powerful discord.js framework to automate and streamline bot development.",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.mjs",
Expand Down Expand Up @@ -35,17 +35,17 @@
"dependencies": {
"iti": "^0.6.0",
"rxjs": "^7.5.6",
"ts-pattern": "^4.0.2",
"ts-pattern": "^4.0.6",
"ts-results-es": "^3.5.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "5.47.1",
"@typescript-eslint/parser": "5.48.0",
"discord.js": ">= ^14.7.x",
"eslint": "8.30.0",
"prettier": "2.8.3",
"tsup": "^6.1.3",
"typescript": "4.9.4",
"discord.js": ">= ^14.7.x"
"typescript": "4.9.4"
},
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions src/handler/contracts/errorHandling.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Observable } from 'rxjs';
import type { Logging } from './logging';
import { useContainerRaw } from '../dependencies/provider';
import util from 'util';
export interface ErrorHandling {
/**
Expand Down Expand Up @@ -36,11 +35,6 @@ export function handleError<C>(crashHandler: ErrorHandling, logging?: Logging) {
// This is done to fit the ErrorHandling contract
const err = pload instanceof Error ? pload : Error(util.format(pload));
if (crashHandler.keepAlive == 0) {
useContainerRaw()
?.disposeAll()
.then(() => {
logging?.info({ message: 'Cleaning container and crashing' });
});
crashHandler.crash(err);
}
//formatted payload
Expand Down
10 changes: 6 additions & 4 deletions src/handler/contracts/moduleManager.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { CommandModuleDefs } from '../../types/module';
import type { CommandType } from '../structures/enums';
import type { ModuleStore } from '../structures/moduleStore';
import type { CommandType, ModuleStore } from '../structures';
import type { Processed } from '../../types/handler';

export interface ModuleManager {
get<T extends CommandType>(
strat: (ms: ModuleStore) => CommandModuleDefs[T] | undefined,
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
): CommandModuleDefs[T] | undefined;
set(strat: (ms: ModuleStore) => void): void;
}

export class DefaultModuleManager implements ModuleManager {
constructor(private moduleStore: ModuleStore) {}
get<T extends CommandType>(strat: (ms: ModuleStore) => CommandModuleDefs[T] | undefined) {
get<T extends CommandType>(
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
) {
return strat(this.moduleStore);
}

Expand Down
3 changes: 2 additions & 1 deletion src/handler/dependencies/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { useContainer } from './provider';
export { single, transient, many } from './lifetimeFunctions';
export { useContainerRaw } from './provider';
50 changes: 50 additions & 0 deletions src/handler/dependencies/lifetimeFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { _const } from '../utilities/functions';

type NotFunction = string | number | boolean | null | undefined | bigint |
readonly any[] | { apply?: never, [k: string]: any } |
{ call?: never, [k: string]: any };

/**
* @deprecated
* @param cb
*/
export function single<T extends NotFunction>(cb: T) : () => T;
/**
* New signature
* @param cb
*/
export function single<T extends () => unknown>(cb: T) : T;
/**
* Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and
* very annoying).
* For future versions, ensure that single is being passed as a **callback!!**
* @param cb
*/
export function single<T>(cb: T) {
if(typeof cb === 'function') return cb;
return () => cb;
}
/**
* @deprecated
* @param cb
* Deprecated signature
*/
export function transient<T extends NotFunction>(cb: T) : () => () => T
export function transient<T extends () => () => unknown>(cb: T) : T;
/**
* 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) | T) {
if(typeof cb !== 'function') return () => () => cb;
return cb;
}

/**
* @deprecated
* @param value
* Please use the transient function instead
*/
// prettier-ignore
export const many = <T>(value: T) => () => _const(value);
126 changes: 54 additions & 72 deletions src/handler/dependencies/provider.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,72 @@
import type { Container } from 'iti';
import { SernError } from '../structures/errors';
import { BehaviorSubject } from 'rxjs';
import * as assert from 'assert';
import type { Dependencies, MapDeps } from '../../types/handler';
import type { Dependencies, DependencyConfiguration, MapDeps } from '../../types/handler';
import SernEmitter from '../sernEmitter';
import { _const, ok } from '../utilities/functions';
import { DefaultErrorHandling, DefaultModuleManager, Logging } from '../contracts';
import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from '../contracts';
import { ModuleStore } from '../structures/moduleStore';
import { Ok, Result } from 'ts-results-es';
import { DefaultLogging } from '../contracts';
import { Result } from 'ts-results-es';
import { BehaviorSubject } from 'rxjs';
import { createContainer } from 'iti';

export const containerSubject = new BehaviorSubject(defaultContainer());

export const containerSubject = new BehaviorSubject<Container<
Dependencies,
Partial<Dependencies>
> | null>(null);
export function composeRoot<T extends Dependencies>(
root: Container<Partial<T>, Partial<Dependencies>>,
exclusion: Set<keyof Dependencies>,
) {
const client = root.get('@sern/client');
assert.ok(client !== undefined, SernError.MissingRequired);
//A utility function checking if a dependency has been declared excluded
const excluded = (key: keyof Dependencies) => exclusion.has(key);
//Wraps a fetch to the container in a Result, deferring the action
const get = <T>(key: keyof Dependencies) => Result.wrap(() => root.get(key) as T);
const getOr = (key: keyof Dependencies, elseAction: () => unknown) => {
//Gets dependency but if an Err, map to a function that upserts.
const dep = get(key).mapErr(() => elseAction);
if (dep.err) {
//Defers upsert until final check here
return dep.val();
}
};
const xGetOr = (key: keyof Dependencies, action: () => unknown) => {
if (excluded(key)) {
get(key) //if dev created a dependency but excluded, deletes on root composition
.andThen(() => Ok(root.delete(key)))
.unwrapOr(ok());
} else {
getOr(key, action);
}
};
xGetOr('@sern/emitter', () =>
root.upsert({
'@sern/emitter': _const(new SernEmitter()),
}),
);
//An "optional" dependency
xGetOr('@sern/logger', () => {
root.upsert({
'@sern/logger': _const(new DefaultLogging()),
/**
* 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 function composeRoot<T extends Dependencies>(conf: DependencyConfiguration<T>) {
//Get the current container. This should have no client or possible logger yet.
const currentContainer = containerSubject.getValue();
const excludeLogger = conf.exclude?.has('@sern/logger');
if(!excludeLogger) {
currentContainer.add({
'@sern/logger' : () => new DefaultLogging()
});
});
xGetOr('@sern/store', () =>
root.upsert({
'@sern/store': _const(new ModuleStore()),
}),
);
xGetOr('@sern/modules', () =>
root.upsert(ctx => ({
'@sern/modules': _const(new DefaultModuleManager(ctx['@sern/store'] as ModuleStore)),
})),
);
xGetOr('@sern/errors', () =>
root.upsert({
'@sern/errors': _const(new DefaultErrorHandling()),
}),
);
//If logger exists, log info, else do nothing.
get<Logging>('@sern/logger')
.map(logger => logger.info({ message: 'All dependencies loaded successfully' }))
.unwrapOr(ok());
}
//Build the container based on the callback provided by the user
const container = conf.build(currentContainer);
//Check if the built container contains @sern/client or throw
// a runtime exception
Result
.wrap(() => container.get('@sern/client'))
.expect(SernError.MissingRequired);

if(!excludeLogger) {
container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
//I'm sorry little one
containerSubject.next(container as any);
}

export function useContainer<T extends Dependencies>() {
const container = containerSubject.getValue()! as unknown as Container<T, {}>;
assert.ok(container !== null, 'useContainer was called before Sern#init');
//weird edge case, why can i not use _const here?
const container = containerSubject.getValue() as Container<T, {}>;
return <V extends (keyof T)[]>(...keys: [...V]) =>
keys.map(key => Result.wrap(() => container.get(key)).unwrapOr(undefined)) as MapDeps<T, V>;
}

/**
* Returns the underlying data structure holding all dependencies.
* Please be careful as this only gets the client's current state.
* Exposes some methods from iti
*/
export function useContainerRaw() {
return containerSubject.getValue();
export function useContainerRaw<T extends Dependencies>() {
return containerSubject.getValue() as Container<T, {}>;
}

/**
* Provides all the defaults for sern to function properly.
* The only user provided dependency needs to be @sern/client
*/
function defaultContainer() {
return createContainer()
.add({ '@sern/errors': () => new DefaultErrorHandling()})
.add({ '@sern/store' : () => new ModuleStore()})
.add(ctx => {
return {
'@sern/modules': () => new DefaultModuleManager(ctx['@sern/store'])
};
})
.add({ '@sern/emitter': () => new SernEmitter()}) as Container<Omit<Dependencies, '@sern/client' | '@sern/logger'>, {}>;
}
Loading