From 0b6240da3c37769ffb4066f310f257a6c9b6af6c Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 16 Feb 2024 15:20:20 -0600 Subject: [PATCH 1/7] ioc proto --- packages/builder/package.json | 1 - .../node_modules/.vitest/deps/_metadata.json | 8 ++ .../node_modules/.vitest/deps/package.json | 3 + .../ioc/node_modules/.vitest/results.json | 1 + packages/ioc/package.json | 18 +++ packages/ioc/src/base.ts | 47 ++++++++ packages/ioc/src/container.ts | 76 ++++++++++++ packages/ioc/src/dependency-injection.ts | 29 +++++ packages/ioc/src/hooks.ts | 5 + packages/ioc/src/index.ts | 2 + packages/ioc/test/index.test.ts | 103 +++++++++++++++++ packages/ioc/tsconfig.json | 109 ++++++++++++++++++ yarn.lock | 9 +- 13 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 packages/ioc/node_modules/.vitest/deps/_metadata.json create mode 100644 packages/ioc/node_modules/.vitest/deps/package.json create mode 100644 packages/ioc/node_modules/.vitest/results.json create mode 100644 packages/ioc/package.json create mode 100644 packages/ioc/src/base.ts create mode 100644 packages/ioc/src/container.ts create mode 100644 packages/ioc/src/dependency-injection.ts create mode 100644 packages/ioc/src/hooks.ts create mode 100644 packages/ioc/src/index.ts create mode 100644 packages/ioc/test/index.test.ts create mode 100644 packages/ioc/tsconfig.json diff --git a/packages/builder/package.json b/packages/builder/package.json index a17fd9b..3bed290 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -17,7 +17,6 @@ "dependencies": { "discord-api-types": "latest" }, - "devDependencies": { "@types/node": "^20.1.0", "microbundle": "^0.15.1", diff --git a/packages/ioc/node_modules/.vitest/deps/_metadata.json b/packages/ioc/node_modules/.vitest/deps/_metadata.json new file mode 100644 index 0000000..f8c874e --- /dev/null +++ b/packages/ioc/node_modules/.vitest/deps/_metadata.json @@ -0,0 +1,8 @@ +{ + "hash": "577958af", + "configHash": "94f40ccb", + "lockfileHash": "9b10afd2", + "browserHash": "79b250d2", + "optimized": {}, + "chunks": {} +} \ No newline at end of file diff --git a/packages/ioc/node_modules/.vitest/deps/package.json b/packages/ioc/node_modules/.vitest/deps/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/packages/ioc/node_modules/.vitest/deps/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/ioc/node_modules/.vitest/results.json b/packages/ioc/node_modules/.vitest/results.json new file mode 100644 index 0000000..dfad382 --- /dev/null +++ b/packages/ioc/node_modules/.vitest/results.json @@ -0,0 +1 @@ +{"version":"1.2.2","results":[[":test/index.test.ts",{"duration":15,"failed":true}]]} \ No newline at end of file diff --git a/packages/ioc/package.json b/packages/ioc/package.json new file mode 100644 index 0000000..1b4e7ea --- /dev/null +++ b/packages/ioc/package.json @@ -0,0 +1,18 @@ +{ + "name": "@sern/ioc", + "version": "1.1.0", + "description": "Dependency Injection system", + "main": "dist/index.js", + "scripts": { + "test": "vitest" + }, + "devDependencies": { + "vitest": "^1.0.0" + }, + "keywords": [], + "author": "", + "license": "ISC", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/ioc/src/base.ts b/packages/ioc/src/base.ts new file mode 100644 index 0000000..550516e --- /dev/null +++ b/packages/ioc/src/base.ts @@ -0,0 +1,47 @@ +import * as assert from 'assert'; +import { CoreContainer } from './container'; + +//SIDE EFFECT: GLOBAL DI +let containerSubject: CoreContainer; + +/** + * @internal + * 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 async function __swap_container(c: CoreContainer) { + if(containerSubject) { + await containerSubject.disposeAll() + } + containerSubject = c; +} + +/** + * @internal + * 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); +} + +export function __init_container(options: { + autowire: boolean; + path?: string | undefined; +}) { + containerSubject = new CoreContainer(options); +} +/** + * Returns the underlying data structure holding all dependencies. + * Exposes methods from iti + * Use the Service API. The container should be readonly + */ +export function useContainerRaw() { + assert.ok( + containerSubject && containerSubject.isReady(), + "Could not find container or container wasn't ready. Did you call makeDependencies?", + ); + return containerSubject; +} + + diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts new file mode 100644 index 0000000..8bdce6c --- /dev/null +++ b/packages/ioc/src/container.ts @@ -0,0 +1,76 @@ +import assert from "node:assert"; +import { hasCallableMethod } from "./hooks"; +import { } from 'node:fs/promises' + +/** + * 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 + */ +export class CoreContainer { + private __singletons = new Map(); + private hooks= new Map(); + private finished_init = false; + constructor(options: { autowire: boolean; path?: string }) { + if(options.autowire) { + + } + } + + 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)) { + //@ts-ignore + this.addHook('init', async () => await insert[hookname]()) + } + } + addSingleton(key: string, insert: object) { + assert(typeof insert === '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: CoreContainer) => object) { + const insert = fn(this); + assert(typeof insert === 'object') + if(!this.__singletons.has(key)){ + this.registerHooks('init', insert) + this.registerHooks('dispose', insert) + this.__singletons.set(key, insert); + return true; + } + return false; + } + + async disposeAll() { + await this.executeHooks('dispose'); + this.hooks.delete('dispose'); + } + + isReady() { return this.finished_init; } + hasKey(key: string) { return this.__singletons.has(key); } + get(key: PropertyKey) : T|undefined { return this.__singletons.get(key); } + + async ready() { + await this.executeHooks('init'); + this.hooks.delete('init'); + this.finished_init = true; + } + + async executeHooks(name: string) { + const hookFunctions = this.hooks.get(name) || []; + for (const hookFunction of hookFunctions) { + await hookFunction(); + } + } +} + diff --git a/packages/ioc/src/dependency-injection.ts b/packages/ioc/src/dependency-injection.ts new file mode 100644 index 0000000..96b1184 --- /dev/null +++ b/packages/ioc/src/dependency-injection.ts @@ -0,0 +1,29 @@ +import assert from 'node:assert'; +import { useContainerRaw } from './base'; + + +/** + * 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 + * @example + * ```ts + * const client = Service('@sern/client'); + * ``` + * @param key a key that corresponds to a dependency registered. + * + */ +export function Service(key: PropertyKey) { + const dep = useContainerRaw().get(key)!; + assert(dep, "Requested key " + String(key) + " returned undefined"); + return dep; +} +/** + * @since 3.0.0 + * The plural version of {@link Service} + * @returns array of dependencies, in the same order of keys provided + */ +export function Services(...keys: [...T]) { + const container = useContainerRaw(); + return keys.map(k => container.get(k)!) as V; +} diff --git a/packages/ioc/src/hooks.ts b/packages/ioc/src/hooks.ts new file mode 100644 index 0000000..2205f54 --- /dev/null +++ b/packages/ioc/src/hooks.ts @@ -0,0 +1,5 @@ + +export function hasCallableMethod(obj: object, name: PropertyKey) { + //@ts-ignore + return Object.hasOwn(obj, name) && typeof obj.init == 'function'; +} diff --git a/packages/ioc/src/index.ts b/packages/ioc/src/index.ts new file mode 100644 index 0000000..5676d56 --- /dev/null +++ b/packages/ioc/src/index.ts @@ -0,0 +1,2 @@ +export { Service, Services } from './dependency-injection'; +export { CoreContainer } from './container' diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts new file mode 100644 index 0000000..813a5d3 --- /dev/null +++ b/packages/ioc/test/index.test.ts @@ -0,0 +1,103 @@ + +import { CoreContainer } from '../src/container'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +describe('CoreContainer Tests', () => { + let coreContainer: CoreContainer; + + beforeEach(() => { + coreContainer = new CoreContainer({ autowire: false }); + }); + + it('Adding and getting singletons', () => { + coreContainer.addSingleton('singletonKey', { value: 'singletonValue' }); + const singleton = coreContainer.get('singletonKey'); + expect(singleton).toEqual({ value: 'singletonValue' }); + }); + + it('Adding disposer for existing key', () => { +// const disposerFn = vi.fn(); +// coreContainer.addSingleton('existingKey', { value: 'existingValue' }); +// coreContainer.addDisposer('existingKey', disposerFn); +// expect(disposerFn).not.toHaveBeenCalled(); + }); + + it('Checking if container is ready', () => { + expect(coreContainer.isReady()).toBe(false); + coreContainer.ready().then(() => { + expect(coreContainer.isReady()).toBe(true); + }); + }); + + it('Adding and getting singletons - async', async () => { + await coreContainer.ready(); + coreContainer.addSingleton('asyncSingletonKey', { value: 'asyncSingletonValue' }); + 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; + + const singletonWithInit = { + value: 'singletonValueWithInit', + init: async () => { + initCount++; + } + }; + + coreContainer.addSingleton('singletonKeyWithInit', singletonWithInit); + + // Call ready twice to ensure hooks are executed only once + await coreContainer.ready(); + await coreContainer.ready(); + + expect(initCount).toBe(1); + }); + + it('Registering and executing hooks - ', async () => { + let initCount = 0; + + const singletonWithInit = { + value: 'singletonValueWithInit', + init: async () => { + initCount++; + } + }; + + coreContainer.addSingleton('singletonKeyWithInit', singletonWithInit); + + // Call ready twice to ensure hooks are executed only once + await coreContainer.ready(); + await coreContainer.ready(); + + expect(initCount).toBe(1); + }); + + + it('wired singleton', async () => { + let fn = vi.fn() + const wiredSingletonFn = (container: CoreContainer) => { + return { value: 'wiredSingletonValue', init: fn }; + }; + const added = coreContainer.addWiredSingleton('wiredSingletonKey', wiredSingletonFn); + expect(added).toBe(true); + const wiredSingleton = coreContainer.get('wiredSingletonKey'); + expect(wiredSingleton).toEqual({ value: 'wiredSingletonValue', init: fn }); + await coreContainer.ready() + await coreContainer.ready() + //@ts-ignore + expect(wiredSingleton.init).toHaveBeenCalledOnce(); + }) + + it('dispose', async () => { + let dfn = vi.fn() + const wiredSingletonFn = { value: 'wiredSingletonValue', dispose: vi.fn() }; + coreContainer.addSingleton('sk', wiredSingletonFn); + + //@ts-ignore + await coreContainer.disposeAll(); + + expect(dfn).toHaveBeenCalledOnce() + }) + +}) diff --git a/packages/ioc/tsconfig.json b/packages/ioc/tsconfig.json new file mode 100644 index 0000000..b165ff8 --- /dev/null +++ b/packages/ioc/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ESNext", /* Specify what module code is generated. */ + // "rootDir": "./", /* 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. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "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. */ + // "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. */ + // "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. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/yarn.lock b/yarn.lock index 169834f..1d016bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2358,12 +2358,11 @@ __metadata: languageName: unknown linkType: soft -"@sern/fs@workspace:packages/fs": +"@sern/ioc@workspace:packages/ioc": version: 0.0.0-use.local - resolution: "@sern/fs@workspace:packages/fs" + resolution: "@sern/ioc@workspace:packages/ioc" dependencies: - "@types/node": ^18.16.0 - vitest: ^1.2.2 + vitest: ^1.0.0 languageName: unknown linkType: soft @@ -8662,7 +8661,7 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^1.2.2": +"vitest@npm:^1.0.0, vitest@npm:^1.2.2": version: 1.2.2 resolution: "vitest@npm:1.2.2" dependencies: From 5e339f594a9986e44aac81aa84dcad69a7cd78b8 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 16 Feb 2024 15:42:37 -0600 Subject: [PATCH 2/7] moving around --- packages/ioc/src/container.ts | 21 ++++++-------- packages/ioc/src/dependency-injection.ts | 29 -------------------- packages/ioc/src/{base.ts => global.ts} | 35 +++++++++++++++++++++--- packages/ioc/src/index.ts | 4 +-- 4 files changed, 42 insertions(+), 47 deletions(-) delete mode 100644 packages/ioc/src/dependency-injection.ts rename packages/ioc/src/{base.ts => global.ts} (56%) diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index 8bdce6c..557d7e4 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -1,19 +1,16 @@ -import assert from "node:assert"; +import assert from "assert"; import { hasCallableMethod } from "./hooks"; import { } from 'node:fs/promises' /** - * 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 + * A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application */ -export class CoreContainer { +export class Container { private __singletons = new Map(); private hooks= new Map(); private finished_init = false; constructor(options: { autowire: boolean; path?: string }) { - if(options.autowire) { - - } + if(options.autowire) { /* noop */ } } addHook(name: string, callback: Function) { @@ -23,10 +20,10 @@ export class CoreContainer { this.hooks.get(name)!.push(callback); } private registerHooks(hookname: string, insert: object) { - if(hasCallableMethod(insert, hookname)) { - //@ts-ignore - this.addHook('init', async () => await insert[hookname]()) - } + if(hasCallableMethod(insert, hookname)) { + //@ts-ignore + this.addHook('init', async () => await insert[hookname]()) + } } addSingleton(key: string, insert: object) { assert(typeof insert === 'object') @@ -39,7 +36,7 @@ export class CoreContainer { return false; } - addWiredSingleton(key: string, fn: (c: CoreContainer) => object) { + addWiredSingleton(key: string, fn: (c: Container) => object) { const insert = fn(this); assert(typeof insert === 'object') if(!this.__singletons.has(key)){ diff --git a/packages/ioc/src/dependency-injection.ts b/packages/ioc/src/dependency-injection.ts deleted file mode 100644 index 96b1184..0000000 --- a/packages/ioc/src/dependency-injection.ts +++ /dev/null @@ -1,29 +0,0 @@ -import assert from 'node:assert'; -import { useContainerRaw } from './base'; - - -/** - * 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 - * @example - * ```ts - * const client = Service('@sern/client'); - * ``` - * @param key a key that corresponds to a dependency registered. - * - */ -export function Service(key: PropertyKey) { - const dep = useContainerRaw().get(key)!; - assert(dep, "Requested key " + String(key) + " returned undefined"); - return dep; -} -/** - * @since 3.0.0 - * The plural version of {@link Service} - * @returns array of dependencies, in the same order of keys provided - */ -export function Services(...keys: [...T]) { - const container = useContainerRaw(); - return keys.map(k => container.get(k)!) as V; -} diff --git a/packages/ioc/src/base.ts b/packages/ioc/src/global.ts similarity index 56% rename from packages/ioc/src/base.ts rename to packages/ioc/src/global.ts index 550516e..edfebff 100644 --- a/packages/ioc/src/base.ts +++ b/packages/ioc/src/global.ts @@ -1,11 +1,10 @@ -import * as assert from 'assert'; +import assert from 'assert'; import { CoreContainer } from './container'; //SIDE EFFECT: GLOBAL DI let containerSubject: CoreContainer; /** - * @internal * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything * then it will swap */ @@ -17,7 +16,6 @@ export async function __swap_container(c: CoreContainer) { } /** - * @internal * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything * then it will swap */ @@ -25,12 +23,17 @@ 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: { autowire: boolean; path?: string | undefined; }) { containerSubject = new CoreContainer(options); } + /** * Returns the underlying data structure holding all dependencies. * Exposes methods from iti @@ -44,4 +47,28 @@ export function useContainerRaw() { return containerSubject; } - +/** + * 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 + * @example + * ```ts + * const client = Service('@sern/client'); + * ``` + * @param key a key that corresponds to a dependency registered. + * + */ +export function Service(key: PropertyKey) { + const dep = useContainerRaw().get(key)!; + assert(dep, "Requested key " + String(key) + " returned undefined"); + return dep; +} +/** + * @since 3.0.0 + * The plural version of {@link Service} + * @returns array of dependencies, in the same order of keys provided + */ +export function Services(...keys: [...T]) { + const container = useContainerRaw(); + return keys.map(k => container.get(k)!) as V; +} diff --git a/packages/ioc/src/index.ts b/packages/ioc/src/index.ts index 5676d56..6430851 100644 --- a/packages/ioc/src/index.ts +++ b/packages/ioc/src/index.ts @@ -1,2 +1,2 @@ -export { Service, Services } from './dependency-injection'; -export { CoreContainer } from './container' +export { Service, Services, __init_container, __swap_container, __add_container } from './global'; +export { Container } from './container' From 6231d9078c9ea18aacb7f444eda08084fbf40c7d Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 23 Feb 2024 20:28:33 -0600 Subject: [PATCH 3/7] ioc proto --- .gitignore | 1 + .../ioc/node_modules/.vitest/deps/_metadata.json | 8 -------- packages/ioc/node_modules/.vitest/deps/package.json | 3 --- packages/ioc/node_modules/.vitest/results.json | 1 - packages/ioc/src/container.ts | 4 +++- packages/ioc/src/hooks.ts | 2 +- packages/ioc/test/index.test.ts | 13 ++++++------- 7 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 packages/ioc/node_modules/.vitest/deps/_metadata.json delete mode 100644 packages/ioc/node_modules/.vitest/deps/package.json delete mode 100644 packages/ioc/node_modules/.vitest/results.json diff --git a/.gitignore b/.gitignore index a634ff5..e9a53bf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ .yarn/cache #.pnp.* node_modules/**/* +packages/ioc/node_modules/* packages/poster/dts/discord.d.ts diff --git a/packages/ioc/node_modules/.vitest/deps/_metadata.json b/packages/ioc/node_modules/.vitest/deps/_metadata.json deleted file mode 100644 index f8c874e..0000000 --- a/packages/ioc/node_modules/.vitest/deps/_metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "hash": "577958af", - "configHash": "94f40ccb", - "lockfileHash": "9b10afd2", - "browserHash": "79b250d2", - "optimized": {}, - "chunks": {} -} \ No newline at end of file diff --git a/packages/ioc/node_modules/.vitest/deps/package.json b/packages/ioc/node_modules/.vitest/deps/package.json deleted file mode 100644 index 3dbc1ca..0000000 --- a/packages/ioc/node_modules/.vitest/deps/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/packages/ioc/node_modules/.vitest/results.json b/packages/ioc/node_modules/.vitest/results.json deleted file mode 100644 index dfad382..0000000 --- a/packages/ioc/node_modules/.vitest/results.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"1.2.2","results":[[":test/index.test.ts",{"duration":15,"failed":true}]]} \ No newline at end of file diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index 557d7e4..36e66bb 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -21,8 +21,9 @@ export class Container { } private registerHooks(hookname: string, insert: object) { if(hasCallableMethod(insert, hookname)) { + console.log(insert) //@ts-ignore - this.addHook('init', async () => await insert[hookname]()) + this.addHook(hookname, () => insert[hookname]()) } } addSingleton(key: string, insert: object) { @@ -65,6 +66,7 @@ export class Container { async executeHooks(name: string) { const hookFunctions = this.hooks.get(name) || []; + console.log(hookFunctions) for (const hookFunction of hookFunctions) { await hookFunction(); } diff --git a/packages/ioc/src/hooks.ts b/packages/ioc/src/hooks.ts index 2205f54..ae2b9d5 100644 --- a/packages/ioc/src/hooks.ts +++ b/packages/ioc/src/hooks.ts @@ -1,5 +1,5 @@ export function hasCallableMethod(obj: object, name: PropertyKey) { //@ts-ignore - return Object.hasOwn(obj, name) && typeof obj.init == 'function'; + return Object.hasOwn(obj, name) && typeof obj[name] == 'function'; } diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts index 813a5d3..250f287 100644 --- a/packages/ioc/test/index.test.ts +++ b/packages/ioc/test/index.test.ts @@ -1,12 +1,12 @@ -import { CoreContainer } from '../src/container'; +import { Container } from '../src/container'; import { describe, it, expect, beforeEach, vi } from 'vitest'; describe('CoreContainer Tests', () => { - let coreContainer: CoreContainer; + let coreContainer: Container; beforeEach(() => { - coreContainer = new CoreContainer({ autowire: false }); + coreContainer = new Container({ autowire: false }); }); it('Adding and getting singletons', () => { @@ -76,7 +76,7 @@ describe('CoreContainer Tests', () => { it('wired singleton', async () => { let fn = vi.fn() - const wiredSingletonFn = (container: CoreContainer) => { + const wiredSingletonFn = (container: Container) => { return { value: 'wiredSingletonValue', init: fn }; }; const added = coreContainer.addWiredSingleton('wiredSingletonKey', wiredSingletonFn); @@ -90,10 +90,9 @@ describe('CoreContainer Tests', () => { }) it('dispose', async () => { - let dfn = vi.fn() - const wiredSingletonFn = { value: 'wiredSingletonValue', dispose: vi.fn() }; + let dfn = vi.fn(); + const wiredSingletonFn = { value: 'wiredSingletonValue', dispose: dfn }; coreContainer.addSingleton('sk', wiredSingletonFn); - //@ts-ignore await coreContainer.disposeAll(); From cd2a670ce98788e4184c5d262dfb99066078bd46 Mon Sep 17 00:00:00 2001 From: jacob Date: Sat, 24 Feb 2024 12:42:46 -0600 Subject: [PATCH 4/7] s --- packages/ioc/test/index.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts index 250f287..11a67d6 100644 --- a/packages/ioc/test/index.test.ts +++ b/packages/ioc/test/index.test.ts @@ -15,12 +15,6 @@ describe('CoreContainer Tests', () => { expect(singleton).toEqual({ value: 'singletonValue' }); }); - it('Adding disposer for existing key', () => { -// const disposerFn = vi.fn(); -// coreContainer.addSingleton('existingKey', { value: 'existingValue' }); -// coreContainer.addDisposer('existingKey', disposerFn); -// expect(disposerFn).not.toHaveBeenCalled(); - }); it('Checking if container is ready', () => { expect(coreContainer.isReady()).toBe(false); From c907a3baa86b3cf4ad593174646554daef37eab3 Mon Sep 17 00:00:00 2001 From: jacob Date: Sat, 24 Feb 2024 12:55:41 -0600 Subject: [PATCH 5/7] polishing up --- packages/ioc/package.json | 2 +- packages/ioc/src/container.ts | 22 ++++++++-------------- packages/ioc/src/global.ts | 16 +++------------- packages/ioc/src/hooks.ts | 5 ----- packages/ioc/src/index.ts | 2 +- 5 files changed, 13 insertions(+), 34 deletions(-) delete mode 100644 packages/ioc/src/hooks.ts diff --git a/packages/ioc/package.json b/packages/ioc/package.json index 1b4e7ea..6f3c975 100644 --- a/packages/ioc/package.json +++ b/packages/ioc/package.json @@ -1,6 +1,6 @@ { "name": "@sern/ioc", - "version": "1.1.0", + "version": "1.0.0", "description": "Dependency Injection system", "main": "dist/index.js", "scripts": { diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index 36e66bb..f9a7c1e 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -1,7 +1,8 @@ -import assert from "assert"; -import { hasCallableMethod } from "./hooks"; -import { } from 'node:fs/promises' +function hasCallableMethod(obj: object, name: PropertyKey) { + //@ts-ignore + return Object.hasOwn(obj, name) && typeof obj[name] == 'function'; +} /** * A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application */ @@ -21,13 +22,14 @@ export class Container { } private registerHooks(hookname: string, insert: object) { if(hasCallableMethod(insert, hookname)) { - console.log(insert) //@ts-ignore this.addHook(hookname, () => insert[hookname]()) } } addSingleton(key: string, insert: object) { - assert(typeof 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) @@ -39,14 +41,7 @@ export class Container { addWiredSingleton(key: string, fn: (c: Container) => object) { const insert = fn(this); - assert(typeof insert === 'object') - if(!this.__singletons.has(key)){ - this.registerHooks('init', insert) - this.registerHooks('dispose', insert) - this.__singletons.set(key, insert); - return true; - } - return false; + return this.addSingleton(key, insert); } async disposeAll() { @@ -66,7 +61,6 @@ export class Container { async executeHooks(name: string) { const hookFunctions = this.hooks.get(name) || []; - console.log(hookFunctions) for (const hookFunction of hookFunctions) { await hookFunction(); } diff --git a/packages/ioc/src/global.ts b/packages/ioc/src/global.ts index edfebff..a550542 100644 --- a/packages/ioc/src/global.ts +++ b/packages/ioc/src/global.ts @@ -1,19 +1,9 @@ import assert from 'assert'; -import { CoreContainer } from './container'; +import { Container } from './container'; //SIDE EFFECT: GLOBAL DI -let containerSubject: CoreContainer; +let containerSubject: Container; -/** - * 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 async function __swap_container(c: CoreContainer) { - if(containerSubject) { - await containerSubject.disposeAll() - } - containerSubject = c; -} /** * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything @@ -31,7 +21,7 @@ export function __init_container(options: { autowire: boolean; path?: string | undefined; }) { - containerSubject = new CoreContainer(options); + containerSubject = new Container(options); } /** diff --git a/packages/ioc/src/hooks.ts b/packages/ioc/src/hooks.ts deleted file mode 100644 index ae2b9d5..0000000 --- a/packages/ioc/src/hooks.ts +++ /dev/null @@ -1,5 +0,0 @@ - -export function hasCallableMethod(obj: object, name: PropertyKey) { - //@ts-ignore - return Object.hasOwn(obj, name) && typeof obj[name] == 'function'; -} diff --git a/packages/ioc/src/index.ts b/packages/ioc/src/index.ts index 6430851..920754c 100644 --- a/packages/ioc/src/index.ts +++ b/packages/ioc/src/index.ts @@ -1,2 +1,2 @@ -export { Service, Services, __init_container, __swap_container, __add_container } from './global'; +export { Service, Services, __init_container, __add_container } from './global'; export { Container } from './container' From 6ccefdb44a8086be6347927b272686a62ea29da0 Mon Sep 17 00:00:00 2001 From: jacob Date: Sat, 24 Feb 2024 12:59:10 -0600 Subject: [PATCH 6/7] make platform agnostic --- packages/ioc/src/global.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/ioc/src/global.ts b/packages/ioc/src/global.ts index a550542..3f11466 100644 --- a/packages/ioc/src/global.ts +++ b/packages/ioc/src/global.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { Container } from './container'; //SIDE EFFECT: GLOBAL DI @@ -21,7 +20,11 @@ export function __init_container(options: { autowire: boolean; path?: string | undefined; }) { + if(containerSubject) { + return false; + } containerSubject = new Container(options); + return true; } /** @@ -30,10 +33,10 @@ export function __init_container(options: { * Use the Service API. The container should be readonly */ export function useContainerRaw() { - assert.ok( - containerSubject && containerSubject.isReady(), - "Could not find container or container wasn't ready. Did you call makeDependencies?", - ); + if (!(containerSubject && containerSubject.isReady())) { + throw new Error("Container wasn't ready or init'd. Please ensure container is ready()"); + } + return containerSubject; } @@ -50,7 +53,9 @@ export function useContainerRaw() { */ export function Service(key: PropertyKey) { const dep = useContainerRaw().get(key)!; - assert(dep, "Requested key " + String(key) + " returned undefined"); + if(!dep) { + throw Error("Requested key " + String(key) + " returned undefined"); + } return dep; } /** From 74024355e2abae047a6c90455d93b19f3d291618 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:37:56 -0500 Subject: [PATCH 7/7] ss --- packages/ioc/src/container.ts | 3 ++- packages/ioc/src/global.ts | 21 +++++++++++++++++---- packages/ioc/test/index.test.ts | 28 ++++++++++++++++++++++++++-- yarn.lock | 2 +- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index f9a7c1e..5b6459f 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -22,8 +22,9 @@ export class Container { } private registerHooks(hookname: string, insert: object) { if(hasCallableMethod(insert, hookname)) { + console.log(hookname) //@ts-ignore - this.addHook(hookname, () => insert[hookname]()) + this.addHook(hookname, async () => await insert[hookname]()) } } addSingleton(key: string, insert: object) { diff --git a/packages/ioc/src/global.ts b/packages/ioc/src/global.ts index 3f11466..d513a27 100644 --- a/packages/ioc/src/global.ts +++ b/packages/ioc/src/global.ts @@ -1,8 +1,25 @@ +<<<<<<< HEAD +======= +import assert from 'assert'; +>>>>>>> 82054aa (sss) import { Container } from './container'; //SIDE EFFECT: GLOBAL DI let containerSubject: Container; +<<<<<<< HEAD +======= +/** + * 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 async function __swap_container(c: Container) { + if(containerSubject) { + await containerSubject.disposeAll() + } + containerSubject = c; +} +>>>>>>> 82054aa (sss) /** * Don't use this unless you know what you're doing. Destroys old containerSubject if it exists and disposes everything @@ -20,11 +37,7 @@ export function __init_container(options: { autowire: boolean; path?: string | undefined; }) { - if(containerSubject) { - return false; - } containerSubject = new Container(options); - return true; } /** diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts index 11a67d6..207f242 100644 --- a/packages/ioc/test/index.test.ts +++ b/packages/ioc/test/index.test.ts @@ -84,13 +84,37 @@ describe('CoreContainer Tests', () => { }) it('dispose', async () => { - let dfn = vi.fn(); + let dfn = vi.fn() const wiredSingletonFn = { value: 'wiredSingletonValue', dispose: dfn }; coreContainer.addSingleton('sk', wiredSingletonFn); - //@ts-ignore + await coreContainer.disposeAll(); expect(dfn).toHaveBeenCalledOnce() }) + it('Checking if container is ready', 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); + + // Call ready twice to ensure hooks are executed only once + await coreContainer.ready(); + await coreContainer.ready(); + + expect(initCount).toBe(1); + }); + }) diff --git a/yarn.lock b/yarn.lock index 356cbe2..9a5b984 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2837,7 +2837,7 @@ __metadata: "typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.0.4#~builtin": version: 5.3.3 - resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin::version=5.3.3&hash=f3b441" + resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin::version=5.3.3&hash=14eedb" bin: tsc: bin/tsc tsserver: bin/tsserver