From cb508c0286dc88c17f8400542b4c59e7e343fac5 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 15 Feb 2020 15:04:01 +0800 Subject: [PATCH] Add factory method --- .eslintrc.js | 23 ++++ .gitignore | 19 +-- .npmignore | 22 --- .npmrc | 1 + .travis.yml | 2 +- README.md | 7 + docs/api.md | 8 ++ index.ts | 365 +++++++++++++++++++++++++++----------------------- package.json | 36 +++-- tsconfig.json | 7 +- tslint.json | 23 ---- 11 files changed, 278 insertions(+), 235 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .npmignore create mode 100644 .npmrc delete mode 100644 tslint.json diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..6942a1c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,23 @@ +module.exports = { + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "tsconfigRootDir": ".", + }, + "plugins": ["@typescript-eslint"], + "rules": { + "quotes": ["error", "double"], + "no-empty": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/camelcase": 0, + "@typescript-eslint/await-thenable": 0, + "@typescript-eslint/no-use-before-define": 0 + }, +}; diff --git a/.gitignore b/.gitignore index 9e0ba36..ff7036e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,15 +2,21 @@ *.swp .DS_Store tmp/ -npm-debug.log -yarn-error.log + .vscode/ .idea/ -node_modules/ -.dist/ -coverage/ + +npm-debug.log +yarn-error.log /package-lock.json /yarn.lock +examples/*/package-lock.json +examples/*/yarn.lock + +node_modules/ + +release/ +coverage/ # Docusaurus @@ -23,6 +29,3 @@ website/yarn.lock website/package-lock.json website/node_modules website/i18n/* - -examples/*/package-lock.json -examples/*/yarn.lock diff --git a/.npmignore b/.npmignore deleted file mode 100644 index b67fbd6..0000000 --- a/.npmignore +++ /dev/null @@ -1,22 +0,0 @@ -*~ -*.swp -.DS_Store -tmp - -.vscode -.idea - -coverage - -.gitattributes -.travis.yml -.editorconfig -tslint.json -tsconfig.json -jest.config.js - -index.test.ts - -examples -docs -website diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index df4fc93..3c839c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: install: - npm install script: -- npm run tslint +- npm run eslint - npm run build - npm run test-with-coveralls jobs: diff --git a/README.md b/README.md index 0ebcef7..0ba57e1 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,13 @@ Clean all cached dependency instances and overrides. Has no parameters. reset() ``` +**factory** + +Make new DI. + +```javascript +const { provide, assign, override, cleanup, reset } = factory(); +``` --- If you have questions or something else for me or this project, maybe architectures questions, improvement ideas or anything else, please make the issue. diff --git a/docs/api.md b/docs/api.md index 2b3a9da..99e98ff 100644 --- a/docs/api.md +++ b/docs/api.md @@ -99,3 +99,11 @@ Clean all cached dependency instances and overrides. Has no parameters. ```javascript reset() ``` + +## factory + +Make new DI. + +```javascript +const { provide, assign, override, cleanup, reset } = factory(); +``` diff --git a/index.ts b/index.ts index cfd234c..863660d 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ import async_hooks, { AsyncHook } from "async_hooks"; import "reflect-metadata"; +type PropertyKey = string | symbol; type ObjectMap = { [key: string]: T; }; @@ -12,199 +13,233 @@ enum DepResolvePhase { Finish, } -export const instances: ObjectMap> = {}; -export const overrides: ObjectMap> = {}; -export const resolvePhases: ObjectMap> = {}; - -export const RootZoneId = 0; -let zoneId: number = RootZoneId; +export function factory() { + const instances: ObjectMap> = {}; + const overrides: ObjectMap> = {}; + const resolvePhases: ObjectMap> = {}; -export function getZoneId(): number { - return zoneId; -} + const RootZoneId = 0; + let zoneId: number = RootZoneId; -export const zoneIndex: ObjectMap = {}; -export const zoneParentIndex: ObjectMap = {}; -let hook: AsyncHook; + function getZoneId(): number { + return zoneId; + } -export function zone(callback: () => T): Promise { - if (typeof hook === "undefined") { - hook = async_hooks.createHook({ - init(asyncId: number, type: any, triggerAsyncId: number) { - const rootAsyncId = zoneIndex[triggerAsyncId]; - rootAsyncId && (zoneIndex[asyncId] = rootAsyncId); - }, - before(asyncId: number) { - zoneId = zoneIndex[asyncId] || RootZoneId; - }, - destroy(asyncId: number) { - delete zoneIndex[asyncId]; - }, - }).enable(); - } - return new Promise((resolve, reject) => { - process.nextTick(async () => { - const asyncId = async_hooks.executionAsyncId(); - zoneParentIndex[asyncId] = zoneIndex[asyncId] || RootZoneId; - zoneId = zoneIndex[asyncId] = asyncId; - try { - await callback(); - resolve(); - } catch (error) { - reject(error); - } - delete zoneParentIndex[asyncId]; - resetZone(asyncId); + const zoneIndex: ObjectMap = {}; + const zoneParentIndex: ObjectMap = {}; + let hook: AsyncHook; + + function zone(callback: () => T): Promise { + if (typeof hook === "undefined") { + hook = async_hooks.createHook({ + init(asyncId: number, type: any, triggerAsyncId: number) { + const rootAsyncId = zoneIndex[triggerAsyncId]; + rootAsyncId && (zoneIndex[asyncId] = rootAsyncId); + }, + before(asyncId: number) { + zoneId = zoneIndex[asyncId] || RootZoneId; + }, + destroy(asyncId: number) { + delete zoneIndex[asyncId]; + }, + }).enable(); + } + return new Promise((resolve, reject) => { + process.nextTick(async () => { + const asyncId = async_hooks.executionAsyncId(); + zoneParentIndex[asyncId] = zoneIndex[asyncId] || RootZoneId; + zoneId = zoneIndex[asyncId] = asyncId; + try { + await callback(); + resolve(); + } catch (error) { + reject(error); + } + delete zoneParentIndex[asyncId]; + resetZone(asyncId); + }); }); - }); -} - -type PropertyKey = string | symbol; + } -export function provide(target: object, propertyKey: PropertyKey): any; -export function provide(dep: Dep): (target: object, propertyKey: PropertyKey) => any; -export function provide(targetOrDep: any, propertyKey?: any): any { - if (typeof propertyKey === "undefined") { - const dep: Dep = targetOrDep; - return (target: object, propertyKey: PropertyKey): any => ( - createProvideDescriptor(dep, propertyKey) + function provide(target: object, propertyKey: PropertyKey): any; + function provide(dep: Dep): (target: object, propertyKey: PropertyKey) => any; + function provide(targetOrDep: any, propertyKey?: any): any { + if (typeof propertyKey === "undefined") { + const dep: Dep = targetOrDep; + return (_target: object, propertyKey: PropertyKey): any => ( + createProvideDescriptor(dep, propertyKey) + ); + } + return createProvideDescriptor( + Reflect.getMetadata("design:type", targetOrDep, propertyKey), + propertyKey, ); } - return createProvideDescriptor( - Reflect.getMetadata("design:type", targetOrDep, propertyKey!), - propertyKey!, - ); -} -export function resolve(dep: Dep): T { - let instance = getInstance(dep); - if (!instance) { - const OverrideDep = getOverride(dep); - if (typeof OverrideDep !== "undefined") { - setInstance(dep, instance = resolve(OverrideDep)); - return instance; - } - setResolvePhase(dep, DepResolvePhase.Start); - if (typeof dep === "function") { - instance = (typeof dep.prototype === "undefined") - ? (dep as () => T)() - : new (dep as new () => T)(); - } else { - instance = dep; + function resolve(dep: Dep): T { + let instance = getInstance(dep); + if (!instance) { + const OverrideDep = getOverride(dep); + if (typeof OverrideDep !== "undefined") { + setInstance(dep, instance = resolve(OverrideDep)); + return instance; + } + setResolvePhase(dep, DepResolvePhase.Start); + if (typeof dep === "function") { + instance = (typeof dep.prototype === "undefined") + ? (dep as () => T)() + : new (dep as new () => T)(); + } else { + instance = dep; + } + setInstance(dep, instance); + setResolvePhase(dep, DepResolvePhase.Finish); } - setInstance(dep, instance); - setResolvePhase(dep, DepResolvePhase.Finish); + return instance; } - return instance; -} - -export function override(from: Dep, to: Dep) { - setOverride(from, to); -} -export function assign(dep: Dep, instance: any) { - setInstance(dep, instance); - const OverrideDep = getOverride(dep); - if (typeof OverrideDep !== "undefined") { - assign(OverrideDep, instance); + function override(from: Dep, to: Dep) { + setOverride(from, to); } -} - -export function cleanup() { - Object.keys(instances).forEach((id) => { - instances[id].clear(); - delete instances[id]; - }); - Object.keys(resolvePhases).forEach((id) => { - resolvePhases[id].clear(); - delete resolvePhases[id]; - }); -} -export function reset() { - cleanup(); - Object.keys(overrides).forEach((id) => { - overrides[id].clear(); - delete overrides[id]; - }); -} - -function resetZone(zoneId: number) { - if (instances[zoneId]) { - instances[zoneId].clear(); - delete instances[zoneId]; - } - if (resolvePhases[zoneId]) { - resolvePhases[zoneId].clear(); - delete resolvePhases[zoneId]; + function assign(dep: Dep, instance: any) { + setInstance(dep, instance); + const OverrideDep = getOverride(dep); + if (typeof OverrideDep !== "undefined") { + assign(OverrideDep, instance); + } } - if (overrides[zoneId]) { - overrides[zoneId].clear(); - delete overrides[zoneId]; + + function cleanup() { + Object.keys(instances).forEach((id) => { + instances[id].clear(); + delete instances[id]; + }); + Object.keys(resolvePhases).forEach((id) => { + resolvePhases[id].clear(); + delete resolvePhases[id]; + }); } -} -function createProvideDescriptor(dep: Dep, propertyKey: PropertyKey) { - return { - get() { - const instance = resolve(dep); - Object.defineProperty(this, propertyKey, { - value: instance, - enumerable: true, - configurable: false, - writable: false, - }); - return instance; - }, - enumerable: true, - configurable: true, - }; -} + function reset() { + cleanup(); + Object.keys(overrides).forEach((id) => { + overrides[id].clear(); + delete overrides[id]; + }); + } -function setResolvePhase(dep: Dep, phase: DepResolvePhase) { - if (typeof resolvePhases[zoneId] === "undefined") { - resolvePhases[zoneId] = new Map(); + function resetZone(zoneId: number) { + if (instances[zoneId]) { + instances[zoneId].clear(); + delete instances[zoneId]; + } + if (resolvePhases[zoneId]) { + resolvePhases[zoneId].clear(); + delete resolvePhases[zoneId]; + } + if (overrides[zoneId]) { + overrides[zoneId].clear(); + delete overrides[zoneId]; + } } - const currentPhase = resolvePhases[zoneId].get(dep); - if (currentPhase === DepResolvePhase.Start && phase === DepResolvePhase.Start) { - throw new Error("Circular dependency detected"); + + function createProvideDescriptor(dep: Dep, propertyKey: PropertyKey) { + return { + get() { + const instance = resolve(dep); + Object.defineProperty(this, propertyKey, { + value: instance, + enumerable: true, + configurable: false, + writable: false, + }); + return instance; + }, + enumerable: true, + configurable: true, + }; } - if (phase === DepResolvePhase.Finish) { - resolvePhases[zoneId].delete(dep); - } else { - resolvePhases[zoneId].set(dep, phase); + + function setResolvePhase(dep: Dep, phase: DepResolvePhase) { + if (typeof resolvePhases[zoneId] === "undefined") { + resolvePhases[zoneId] = new Map(); + } + const currentPhase = resolvePhases[zoneId].get(dep); + if (currentPhase === DepResolvePhase.Start && phase === DepResolvePhase.Start) { + throw new Error("Circular dependency detected"); + } + if (phase === DepResolvePhase.Finish) { + resolvePhases[zoneId].delete(dep); + } else { + resolvePhases[zoneId].set(dep, phase); + } } -} -function setInstance(dep: Dep, instance: any) { - if (typeof instances[zoneId] === "undefined") { - instances[zoneId] = new Map(); + function setInstance(dep: Dep, instance: any) { + if (typeof instances[zoneId] === "undefined") { + instances[zoneId] = new Map(); + } + instances[zoneId].set(dep, instance); } - instances[zoneId].set(dep, instance); -} -function getInstance(dep: Dep): any { - if (typeof instances[zoneId] !== "undefined") { - return instances[zoneId].get(dep); + function getInstance(dep: Dep): any { + if (typeof instances[zoneId] !== "undefined") { + return instances[zoneId].get(dep); + } } -} -function setOverride(from: Dep, to: Dep) { - if (typeof overrides[zoneId] === "undefined") { - overrides[zoneId] = new Map(); + function setOverride(from: Dep, to: Dep) { + if (typeof overrides[zoneId] === "undefined") { + overrides[zoneId] = new Map(); + } + overrides[zoneId].set(from, to); } - overrides[zoneId].set(from, to); -} -function getOverride(from: Dep): Dep | undefined { - let id = zoneId; - while (typeof id !== "undefined") { - if (typeof overrides[id] !== "undefined") { - const to = overrides[id].get(from); - if (typeof to !== "undefined") { - return to; + function getOverride(from: Dep): Dep | undefined { + let id = zoneId; + while (typeof id !== "undefined") { + if (typeof overrides[id] !== "undefined") { + const to = overrides[id].get(from); + if (typeof to !== "undefined") { + return to; + } } + id = zoneParentIndex[id]; } - id = zoneParentIndex[id]; + } + + return { + instances, + overrides, + resolvePhases, + RootZoneId, + getZoneId, + zoneIndex, + zoneParentIndex, + zone, + provide, + resolve, + override, + assign, + cleanup, + reset } } + +export const { + instances, + overrides, + resolvePhases, + RootZoneId, + getZoneId, + zoneIndex, + zoneParentIndex, + zone, + provide, + resolve, + override, + assign, + cleanup, + reset +} = factory(); diff --git a/package.json b/package.json index ca8b433..dea5f9d 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,28 @@ { "name": "node-provide", - "version": "0.4.4", + "version": "1.0.0", "description": "Async context based Dependency Injection for Node.JS", "repository": { "url": "https://github.com/betula/node-provide" }, "source": "./index.ts", - "main": ".dist/index.js", - "types": ".dist/index.d.ts", + "main": "./release/index.js", + "types": "./release/index.d.ts", + "files": [ + "/release", + "/index.ts", + "/jest-cleanup-after-each.js", + "/README.md" + ], + "engines" : { + "node" : ">=8.0.0" + }, "scripts": { "build": "npx tsc", "test": "npx jest --coverage", "test-with-coveralls": "npx jest --coverage && cat ./coverage/lcov.info | coveralls", - "tslint": "tslint -c tslint.json -p tsconfig.json", - "prepare": "npm run tslint && npm run build && npm run test" + "eslint": "npx eslint index.ts", + "prepare": "npm run eslint && npm run build && npm run test" }, "keywords": [ "di", @@ -31,13 +40,14 @@ "reflect-metadata": "^0.1.13" }, "devDependencies": { - "@types/node": "^12.12.17", - "typescript": "^3.7.3", - "tslint": "^5.20.1", - "tslint-config-airbnb": "^5.11.2", - "jest": "^24.9.0", - "ts-jest": "^24.2.0", - "@types/jest": "^24.0.23", - "coveralls": "^3.0.9" + "@types/jest": "^25.1.2", + "@types/node": "^13.7.1", + "@typescript-eslint/eslint-plugin": "^2.19.2", + "@typescript-eslint/parser": "^2.19.2", + "coveralls": "^3.0.9", + "eslint": "^6.8.0", + "jest": "^25.1.0", + "ts-jest": "^25.2.0", + "typescript": "^3.7.5" } } diff --git a/tsconfig.json b/tsconfig.json index 1d23950..70f43f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,17 +2,17 @@ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./.dist", /* Redirect output structure to the directory. */ + "outDir": "./release", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -61,6 +61,7 @@ "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ }, "exclude": [ + "release/*", "examples/*", "**/*.js", "**/*.test.ts" diff --git a/tslint.json b/tslint.json deleted file mode 100644 index f120c6d..0000000 --- a/tslint.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint-config-airbnb" - ], - "jsRules": {}, - "rules": { - "quotemark": [ - true, - "double" - ], - "variable-name": false, - "ter-arrow-parens": [true, "always"], - "max-line-length": false, - "no-this-assignment": false - }, - "rulesDirectory": [], - "linterOptions": { - "exclude": [ - "**/*.js" - ] - } -}