mirror of
https://github.com/sern-handler/tools
synced 2026-06-05 17:06:57 +00:00
Merge branch 'main' into feat/publisher
This commit is contained in:
2
.github/workflows/npm-publish.yml
vendored
2
.github/workflows/npm-publish.yml
vendored
@@ -29,11 +29,11 @@ jobs:
|
||||
- run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
- run: yarn set version classic
|
||||
working-directory: ./packages/${{ github.event.inputs.package }}
|
||||
- run: yarn install --immutable
|
||||
- run: yarn build
|
||||
working-directory: ./packages/${{ github.event.inputs.package }}
|
||||
- run: yarn test
|
||||
working-directory: ./packages/${{ github.event.inputs.package }}
|
||||
- run: yarn install --immutable
|
||||
- run: yarn publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
||||
#.pnp.*
|
||||
node_modules/**/*
|
||||
packages/ioc/node_modules/*
|
||||
packages/ioc/dist
|
||||
packages/poster/dts/discord.d.ts
|
||||
packages/**/node_modules
|
||||
packages/**/dist
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
{
|
||||
"name": "@sern/ioc",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.3",
|
||||
"description": "Dependency Injection system",
|
||||
"main": "dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
"." : {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./global": {
|
||||
"import": "./dist/global.js",
|
||||
"require": "./dist/global.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest --run",
|
||||
"tdd": "vitest",
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
|
||||
function hasCallableMethod(obj: object, name: PropertyKey) {
|
||||
// object will always be defined
|
||||
return typeof obj[name] == 'function';
|
||||
}
|
||||
/**
|
||||
* A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application
|
||||
*/
|
||||
export class Container {
|
||||
private __singletons = new Map<PropertyKey, any>();
|
||||
private hooks= new Map<string, Function[]>();
|
||||
private finished_init = false;
|
||||
constructor(options: { autowire: boolean; path?: string }) {
|
||||
if(options.autowire) { /* noop */ }
|
||||
}
|
||||
|
||||
addHook(name: string, callback: Function) {
|
||||
if (!this.hooks.has(name)) {
|
||||
this.hooks.set(name, []);
|
||||
}
|
||||
this.hooks.get(name)!.push(callback);
|
||||
}
|
||||
private registerHooks(hookname: string, insert: object) {
|
||||
if(hasCallableMethod(insert, hookname)) {
|
||||
console.log(hookname)
|
||||
//@ts-ignore
|
||||
this.addHook(hookname, async () => await insert[hookname]())
|
||||
}
|
||||
}
|
||||
addSingleton(key: string, insert: object) {
|
||||
if(typeof insert !== 'object') {
|
||||
throw Error("Inserted object must be an object");
|
||||
}
|
||||
if(!this.__singletons.has(key)){
|
||||
this.registerHooks('init', insert)
|
||||
this.registerHooks('dispose', insert)
|
||||
this.__singletons.set(key, insert);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addWiredSingleton(key: string, fn: (c: Container) => object) {
|
||||
const insert = fn(this);
|
||||
return this.addSingleton(key, insert);
|
||||
}
|
||||
|
||||
async disposeAll() {
|
||||
await this.executeHooks('dispose');
|
||||
this.hooks.delete('dispose');
|
||||
}
|
||||
|
||||
isReady() { return this.finished_init; }
|
||||
hasKey(key: string) { return this.__singletons.has(key); }
|
||||
get<T>(key: PropertyKey) : T|undefined { return this.__singletons.get(key); }
|
||||
|
||||
async ready() {
|
||||
await this.executeHooks('init');
|
||||
this.hooks.delete('init');
|
||||
this.finished_init = true;
|
||||
}
|
||||
|
||||
deps<T extends Record<string,any>>(): T {
|
||||
return Object.fromEntries(this.__singletons) as T
|
||||
}
|
||||
|
||||
async executeHooks(name: string) {
|
||||
const hookFunctions = this.hooks.get(name) || [];
|
||||
for (const hookFunction of hookFunctions) {
|
||||
await hookFunction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Container } from './container';
|
||||
import { Container } from './index';
|
||||
|
||||
//SIDE EFFECT: GLOBAL DI
|
||||
let containerSubject: Container;
|
||||
@@ -14,23 +14,18 @@ export async function __swap_container(c: Container) {
|
||||
containerSubject = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything
|
||||
* then it will swap
|
||||
*/
|
||||
export function __add_container(key: string, v: object) {
|
||||
containerSubject.addSingleton(key, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the global api.
|
||||
* Once this is finished, the Service api and the other global api is available
|
||||
*/
|
||||
export function __init_container(options: {
|
||||
export async function __init_container(options: {
|
||||
autowire: boolean;
|
||||
path?: string | undefined;
|
||||
}) {
|
||||
containerSubject = new Container(options);
|
||||
await containerSubject.ready()
|
||||
return containerSubject
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +44,7 @@ export function useContainerRaw() {
|
||||
/**
|
||||
* The Service api, retrieve from the globally init'ed container
|
||||
* Note: this method only works AFTER your container has been initiated
|
||||
* @since 3.0.0
|
||||
* @since 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* const client = Service('@sern/client');
|
||||
@@ -65,7 +60,7 @@ export function Service<const T>(key: PropertyKey) {
|
||||
return dep;
|
||||
}
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @since 1.0.0
|
||||
* The plural version of {@link Service}
|
||||
* @returns array of dependencies, in the same order of keys provided
|
||||
*/
|
||||
|
||||
@@ -1,2 +1,107 @@
|
||||
export { Service, Services, __init_container, __add_container } from './global';
|
||||
export { Container } from './container'
|
||||
/**
|
||||
* A semi-generic container that provides error handling, emitter, and module store.
|
||||
* For the handler to operate correctly, The only user provided dependency needs to be @sern/client
|
||||
*/
|
||||
function hasCallableMethod(obj: object, name: PropertyKey) {
|
||||
//@ts-ignore
|
||||
return typeof obj[name] == 'function';
|
||||
}
|
||||
/**
|
||||
* A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application
|
||||
*/
|
||||
export class Container {
|
||||
private __singletons = new Map<PropertyKey, any>();
|
||||
//hooks are Maps of string -> object, where object is a reference to an object in __singletons
|
||||
private hooks= new Map<string, object[]>();
|
||||
private finished_init = false;
|
||||
constructor(options: { autowire: boolean; path?: string }) {
|
||||
if(options.autowire) { /* noop */ }
|
||||
}
|
||||
|
||||
addHook(name: string, callback: object) {
|
||||
if (!this.hooks.has(name)) {
|
||||
this.hooks.set(name, []);
|
||||
}
|
||||
this.hooks.get(name)!.push(callback);
|
||||
}
|
||||
private registerHooks(hookname: string, insert: object) {
|
||||
|
||||
if(hasCallableMethod(insert, hookname)) {
|
||||
//@ts-ignore
|
||||
this.addHook(hookname, insert)
|
||||
}
|
||||
}
|
||||
|
||||
addSingleton(key: string, insert: object) {
|
||||
if(typeof insert !== 'object') {
|
||||
throw Error("Inserted object must be an object");
|
||||
}
|
||||
if(!this.__singletons.has(key)) {
|
||||
this.registerHooks('init', insert)
|
||||
this.registerHooks('dispose', insert)
|
||||
this.__singletons.set(key, insert);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addWiredSingleton(key: string, fn: (c: Record<string,unknown>) => object) {
|
||||
const insert = fn(this.deps());
|
||||
return this.addSingleton(key, insert);
|
||||
}
|
||||
|
||||
async disposeAll() {
|
||||
await this.executeHooks('dispose');
|
||||
this.hooks.delete('dispose');
|
||||
}
|
||||
|
||||
isReady() { return this.finished_init; }
|
||||
hasKey(key: string) { return this.__singletons.has(key); }
|
||||
get<T>(key: PropertyKey) : T|undefined { return this.__singletons.get(key); }
|
||||
|
||||
async ready() {
|
||||
await this.executeHooks('init');
|
||||
this.hooks.delete('init');
|
||||
this.finished_init = true;
|
||||
}
|
||||
|
||||
deps<T extends Record<string,any>>(): T {
|
||||
return Object.fromEntries(this.__singletons) as T
|
||||
}
|
||||
|
||||
private async executeHooks(name: string) {
|
||||
const hookFunctions = this.hooks.get(name) || [];
|
||||
for (const hookObject of hookFunctions) {
|
||||
//@ts-ignore .registerHooks verifies the hookObject hasCallableMethod
|
||||
await hookObject[name]();
|
||||
}
|
||||
}
|
||||
|
||||
swap(key: string, swp: object) {
|
||||
if (typeof swp !== 'object') {
|
||||
throw Error("Inserted object must be an object");
|
||||
}
|
||||
|
||||
const existing = this.__singletons.get(key);
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
// check if there's dispose hook, and call it
|
||||
if (hasCallableMethod(existing, 'dispose')) {
|
||||
//this should technically be awaited to ensure synchronicity of swap
|
||||
// but i dont want to ruin the function signature of swap.
|
||||
existing.dispose();
|
||||
// get the index of the existing singleton, now delete the dispose hook at that index
|
||||
// .indexOf is safe because we only store singletons, and it should be a reference to
|
||||
// the original object in this.__singletons
|
||||
const hookIndex = this.hooks.get('dispose')!.indexOf(existing);
|
||||
if (hookIndex > -1) {
|
||||
this.hooks.get('dispose')!.splice(hookIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.__singletons.set(key, swp);
|
||||
this.registerHooks('init', swp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
import { Container } from '../src';
|
||||
import { describe, it, expect, beforeEach, vi, Mock, afterEach } from 'vitest';
|
||||
|
||||
import { Container } from '../src/container';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
class SingletonCheese {
|
||||
dispose() {
|
||||
return this.value
|
||||
}
|
||||
constructor(public value: string){}
|
||||
}
|
||||
describe('CoreContainer Tests', () => {
|
||||
let coreContainer: Container;
|
||||
|
||||
let singletonWInit: { init: Mock<any, any>; value: string }
|
||||
let singletonWDispose: SingletonCheese
|
||||
beforeEach(() => {
|
||||
coreContainer = new Container({ autowire: false });
|
||||
singletonWInit = {
|
||||
value: 'singletonWithInit',
|
||||
init: vi.fn()
|
||||
};
|
||||
singletonWDispose = new SingletonCheese('singletonWithDispose')
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('Adding and getting singletons', () => {
|
||||
coreContainer.addSingleton('singletonKey', { value: 'singletonValue' });
|
||||
const singleton = coreContainer.get('singletonKey');
|
||||
@@ -29,6 +44,7 @@ describe('CoreContainer Tests', () => {
|
||||
const singleton = coreContainer.get('asyncSingletonKey');
|
||||
expect(singleton).toEqual({ value: 'asyncSingletonValue' });
|
||||
})
|
||||
|
||||
it('Registering and executing hooks - init should be called once after ready', async () => {
|
||||
let initCount = 0;
|
||||
|
||||
@@ -70,51 +86,105 @@ describe('CoreContainer Tests', () => {
|
||||
|
||||
it('wired singleton', async () => {
|
||||
let fn = vi.fn()
|
||||
const wiredSingletonFn = (container: Container) => {
|
||||
const wiredSingletonFn = (container: unknown) => {
|
||||
return { value: 'wiredSingletonValue', init: fn };
|
||||
};
|
||||
const added = coreContainer.addWiredSingleton('wiredSingletonKey', wiredSingletonFn);
|
||||
expect(added).toBe(true);
|
||||
const wiredSingleton = coreContainer.get('wiredSingletonKey');
|
||||
|
||||
const wiredSingleton = coreContainer.get<Record<string, unknown>>('wiredSingletonKey')!;
|
||||
expect(wiredSingleton).toEqual({ value: 'wiredSingletonValue', init: fn });
|
||||
await coreContainer.ready()
|
||||
await coreContainer.ready()
|
||||
//@ts-ignore
|
||||
|
||||
await coreContainer.ready();
|
||||
await coreContainer.ready();
|
||||
|
||||
expect(wiredSingleton.init).toHaveBeenCalledOnce();
|
||||
})
|
||||
|
||||
it('dispose', async () => {
|
||||
let dfn = vi.fn()
|
||||
let count = 0;
|
||||
const wiredSingletonFn = { value: 'wiredSingletonValue', dispose: dfn };
|
||||
coreContainer.addSingleton('sk', wiredSingletonFn);
|
||||
|
||||
const added = coreContainer.addSingleton('sk', wiredSingletonFn);
|
||||
expect(added).toBe(true);
|
||||
|
||||
await coreContainer.disposeAll();
|
||||
await coreContainer.disposeAll();
|
||||
|
||||
expect(dfn).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('Checking if container is ready', async () => {
|
||||
it('Checking if container is ready - async', async () => {
|
||||
expect(coreContainer.isReady()).toBe(false);
|
||||
await coreContainer.ready();
|
||||
expect(coreContainer.isReady()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Registering and executing hooks - init should be called once after ready', async () => {
|
||||
let initCount = 0;
|
||||
|
||||
const singletonWithInit = {
|
||||
value: 'singletonValueWithInit',
|
||||
init: async () => {
|
||||
initCount++;
|
||||
}
|
||||
};
|
||||
|
||||
coreContainer.addSingleton('singletonKeyWithInit', singletonWithInit);
|
||||
coreContainer.addSingleton('singletonKeyWithInit', singletonWInit);
|
||||
|
||||
// Call ready twice to ensure hooks are executed only once
|
||||
await coreContainer.ready();
|
||||
await coreContainer.ready();
|
||||
|
||||
expect(initCount).toBe(1);
|
||||
expect(singletonWInit.init).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should return false because not swapping anything', () => {
|
||||
const swap = coreContainer.swap('singletonKeyWithInit', singletonWInit);
|
||||
expect(swap).toBe(false);
|
||||
})
|
||||
it('should return true because not swapping anything', () => {
|
||||
coreContainer.addSingleton('singletonKeyWithInit', singletonWInit);
|
||||
const singletonWithInit2 = {
|
||||
value: 'singletonValueWithInit2',
|
||||
init: vi.fn()
|
||||
};
|
||||
const swap = coreContainer.swap('singletonKeyWithInit', singletonWithInit2);
|
||||
expect(swap).toBe(true);
|
||||
})
|
||||
it('should swap object with another', () => {
|
||||
coreContainer.addSingleton('singleton', singletonWInit)
|
||||
const singletonWithInit2 = {
|
||||
value: 'singletonValueWithInit2',
|
||||
init: vi.fn()
|
||||
};
|
||||
coreContainer.swap('singleton', singletonWithInit2)
|
||||
expect(coreContainer.get<Record<string, unknown>>('singleton')).toBe(singletonWithInit2)
|
||||
})
|
||||
|
||||
it('should swap object, calling dispose hook', () => {
|
||||
coreContainer.addSingleton('singleton', singletonWDispose);
|
||||
const singletonWithDispose2 = {
|
||||
value: 'singletonValueWithDispose2',
|
||||
dispose: vi.fn()
|
||||
};
|
||||
|
||||
const singletonWithDispose3 = {
|
||||
value: 'singletonValueWithDispose3',
|
||||
dispose: vi.fn()
|
||||
};
|
||||
|
||||
coreContainer.addSingleton('singletonWithDispose3', singletonWithDispose3);
|
||||
vi.spyOn(singletonWDispose, 'dispose')
|
||||
coreContainer.swap('singleton', singletonWithDispose2);
|
||||
|
||||
expect(singletonWDispose.dispose).toHaveBeenCalledOnce();
|
||||
expect(coreContainer.get<Record<string, unknown>>('singleton')).toBe(singletonWithDispose2);
|
||||
expect(singletonWithDispose2.dispose).not.toHaveBeenCalledOnce();
|
||||
expect(singletonWithDispose3.dispose).not.toHaveBeenCalledOnce();
|
||||
})
|
||||
it('should swap object, maintaining reference to `this`', () => {
|
||||
coreContainer.addSingleton('singleton', singletonWDispose);
|
||||
const singletonWithDispose2 = {
|
||||
value: 'singletonValueWithDispose2',
|
||||
dispose: vi.fn()
|
||||
};
|
||||
const spiedDispose = vi.spyOn(singletonWDispose, 'dispose')
|
||||
const swapped = coreContainer.swap('singleton', singletonWithDispose2);
|
||||
expect(spiedDispose.mock.results[0].value).toEqual('singletonWithDispose');
|
||||
expect(coreContainer.get<Record<string, unknown>>('singleton')).toBe(singletonWithDispose2);
|
||||
expect(singletonWithDispose2.dispose).not.toHaveBeenCalledOnce();
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
/* Modules */
|
||||
"module": "ESNext", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
@@ -49,10 +49,10 @@
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
@@ -105,5 +105,6 @@
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
},
|
||||
"include": ["./src", "./src/**/*.d.ts"]
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ sidebar:
|
||||
---
|
||||
|
||||
|
||||
# @sern/localizer
|
||||
|
||||
A localization module for managing translations and providing localized content in your application.
|
||||
|
||||
## Installation
|
||||
@@ -16,21 +14,43 @@ A localization module for managing translations and providing localized content
|
||||
npm i @sern/localizer
|
||||
```
|
||||
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
**Initializing the Localizer**
|
||||
```ts
|
||||
import { makeDependencies } from '@sern/handler';
|
||||
import { Localization } from '@sern/localizer';
|
||||
|
||||
await makeDependencies(({ add }) => {
|
||||
add('localizer', Localization());
|
||||
});
|
||||
```
|
||||
<Tabs>
|
||||
<TabItem label ="src/index.js">
|
||||
```ts {2} {6}
|
||||
import { makeDependencies } from '@sern/handler';
|
||||
import { Localization } from '@sern/localizer';
|
||||
|
||||
await makeDependencies(({ add }) => {
|
||||
// add other deps
|
||||
add('localizer', Localization());
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="src/dependencies.d.ts">
|
||||
```ts {5}
|
||||
import type { Logging, CoreDependencies } from '@sern/handler'
|
||||
import type { Localizer } from '@sern/localizer'
|
||||
declare global {
|
||||
interface Dependencies extends CoreDependencies {
|
||||
localizer: Localizer;
|
||||
}
|
||||
}
|
||||
export {}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This localizer is **FILE BASED**.
|
||||
Create the directory `assets/locals`. Each json file in here must be named after the `locale`
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Spanish">
|
||||
|
||||
@@ -2448,7 +2448,7 @@ __metadata:
|
||||
|
||||
"typescript@patch:typescript@^5.0.0#~builtin<compat/typescript>":
|
||||
version: 5.4.5
|
||||
resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin<compat/typescript>::version=5.4.5&hash=14eedb"
|
||||
resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin<compat/typescript>::version=5.4.5&hash=f3b441"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
|
||||
Reference in New Issue
Block a user