From bad10c5eb6420d69c5b137d4e147ffa073b81ee7 Mon Sep 17 00:00:00 2001 From: Eugene Daragan Date: Sat, 30 Mar 2024 16:50:37 +0100 Subject: [PATCH] refactoring WIP --- package-lock.json | 725 ++++++++++++++++++++++- package.json | 13 +- src/container.ts | 183 ++++++ src/errors.ts | 23 + src/index.ts | 5 + src/scopes.ts | 39 ++ src/token.ts | 1 + src/types.ts | 46 ++ test/dioma.test.ts | 1362 ++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 6 +- vite.config.js | 17 + 11 files changed, 2404 insertions(+), 16 deletions(-) create mode 100644 src/container.ts create mode 100644 src/errors.ts create mode 100644 src/index.ts create mode 100644 src/scopes.ts create mode 100644 src/token.ts create mode 100644 src/types.ts create mode 100644 test/dioma.test.ts create mode 100644 vite.config.js diff --git a/package-lock.json b/package-lock.json index 466f53f..a4f5576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "dioma", - "version": "0.0.4", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dioma", - "version": "0.0.4", + "version": "0.3.0", + "license": "MIT", "devDependencies": { - "@types/node": "^20.11.30", "@vitest/coverage-v8": "^1.4.0", "@vitest/ui": "latest", - "typescript": "^5.4.2", "vite": "latest", + "vite-plugin-dts": "^3.8.1", "vitest": "latest" } }, @@ -516,6 +516,99 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@microsoft/api-extractor": { + "version": "7.43.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.0.tgz", + "integrity": "sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/tsdoc": "0.14.2", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "4.0.2", + "@rushstack/rig-package": "0.5.2", + "@rushstack/terminal": "0.10.0", + "@rushstack/ts-command-line": "4.19.1", + "lodash": "~4.17.15", + "minimatch": "~3.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.4.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.28.13", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", + "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "4.0.2" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "dev": true + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", + "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.2", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -557,6 +650,34 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", @@ -726,12 +847,110 @@ "win32" ] }, + "node_modules/@rushstack/node-core-library": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", + "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "dev": true, + "dependencies": { + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "z-schema": "~5.0.2" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", + "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", + "dev": true, + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", + "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "dev": true, + "dependencies": { + "@rushstack/node-core-library": "4.0.2", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.1.tgz", + "integrity": "sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==", + "dev": true, + "dependencies": { + "@rushstack/terminal": "0.10.0", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -749,6 +968,8 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -871,6 +1092,118 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", + "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", + "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.21", + "@vue/shared": "3.4.21" + } + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "dev": true + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -892,6 +1225,22 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -904,6 +1253,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -980,6 +1338,22 @@ "node": "*" } }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1006,6 +1380,12 @@ "node": ">= 8" } }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1044,6 +1424,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -1114,6 +1506,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -1130,6 +1528,12 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -1163,6 +1567,20 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1183,6 +1601,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -1236,6 +1663,12 @@ "node": ">= 6" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1245,6 +1678,27 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -1260,6 +1714,15 @@ "node": ">=16.17.0" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1276,6 +1739,18 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1374,18 +1849,45 @@ "node": ">=8" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, "node_modules/js-tokens": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true + }, "node_modules/local-pkg": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", @@ -1402,6 +1904,24 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -1540,6 +2060,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1624,6 +2150,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1642,6 +2174,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -1728,6 +2266,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1754,6 +2301,23 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -1887,6 +2451,15 @@ "node": ">= 10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -1896,6 +2469,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -1908,6 +2487,15 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -1920,6 +2508,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", @@ -1944,6 +2544,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -2044,7 +2656,27 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } }, "node_modules/v8-to-istanbul": { "version": "9.2.0", @@ -2060,6 +2692,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vite": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", @@ -2137,6 +2778,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-dts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-3.8.1.tgz", + "integrity": "sha512-zEYyQxH7lKto1VTKZHF3ZZeOPkkJgnMrePY4VxDHfDSvDjmYMMfWjZxYmNwW8QxbaItWJQhhXY+geAbyNphI7g==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor": "7.43.0", + "@rollup/pluginutils": "^5.1.0", + "@vue/language-core": "^1.8.27", + "debug": "^4.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.8", + "vue-tsc": "^1.8.27" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", @@ -2202,6 +2870,33 @@ } } }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2256,6 +2951,26 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dev": true, + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } } } } diff --git a/package.json b/package.json index 1bcbea6..a931f86 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,16 @@ "test:coverage": "vitest --coverage" }, "devDependencies": { - "@types/node": "^20.11.30", "@vitest/coverage-v8": "^1.4.0", "@vitest/ui": "latest", - "typescript": "^5.4.2", "vite": "latest", - "vitest": "latest" + "vitest": "latest", + "vite-plugin-dts": "^3.8.1" }, - "types": "dist/dioma.d.ts", - "module": "dist/es6/dioma.js", - "main": "dist/common/dioma.js", - "umd:main": "dist/umd/dioma.js", + "types": "dist/index.d.ts", + "module": "dist/dioma.mjs", + "main": "dist/dioma.js", + "umd:main": "dist/dioma.umd.js", "license": "MIT", "repository": { "type": "git", diff --git a/src/container.ts b/src/container.ts new file mode 100644 index 0000000..fe35a3b --- /dev/null +++ b/src/container.ts @@ -0,0 +1,183 @@ +import type { ScopedClass, TokenDescriptor, ScopeHandler, IContainer } from "./types"; +import { Scopes } from "./scopes"; +import { Token } from "./token"; +import { + AsyncCycleDependencyError, + CycleDependencyError, + TokenNotRegisteredError, +} from "./errors"; + +type TokenOrClass = Token | ScopedClass; + +type TokenDescriptorWithContainer = TokenDescriptor & { + container: Container; +}; + +const MAX_LOOP_COUNT = 100; + +export class Container implements IContainer { + private instances = new WeakMap(); + + private resolutionContainer: Container | null = null; + + private resolutionSet = new Set(); + + private pendingPromiseMap = new Map>>(); + + private tokenDescriptorMap = new Map(); + + constructor(private parentContainer: Container | null = null, public name?: string) {} + + loopCounter = 0; + + childContainer = (name?: string) => { + return new Container(this, name); + }; + + getInstance(cls: any, args: any[] = []) { + let instance = null; + let container: Container | null = this; + + while (!instance && container) { + instance = container.instances.get(cls); + container = container.parentContainer; + } + + if (!instance) { + instance = new cls(...args); + + this.instances.set(cls, instance); + } + + return instance; + } + + private getTokenDescriptor( + clsOrToken: ScopedClass | Token + ): TokenDescriptorWithContainer | undefined { + let tokenDescriptor = this.tokenDescriptorMap.get(clsOrToken); + + if (!tokenDescriptor && this.parentContainer) { + tokenDescriptor = this.parentContainer.getTokenDescriptor(clsOrToken); + } + + return tokenDescriptor; + } + + injectImpl( + clsOrToken: T | Token, + args: Args, + resolutionContainer = this.resolutionContainer + ): InstanceType { + this.resolutionContainer = resolutionContainer || new Container(); + + let cls = clsOrToken as ScopedClass; + let scope: ScopeHandler; + let container: Container | null = this; + + const descriptor = this.getTokenDescriptor(clsOrToken); + + if (descriptor) { + container = descriptor.container; + cls = descriptor.class; + scope = descriptor.scope || cls.scope || Scopes.Transient(); + } else { + if (clsOrToken instanceof Token) { + throw new TokenNotRegisteredError(); + } + + scope = cls.scope || Scopes.Transient(); + } + + try { + if (this.resolutionSet.has(clsOrToken)) { + throw new CycleDependencyError(); + } + + this.resolutionSet.add(clsOrToken); + + return scope(cls, args, container, this.resolutionContainer); + } finally { + this.resolutionSet.delete(clsOrToken); + this.resolutionContainer = resolutionContainer; + + if (!resolutionContainer) { + this.loopCounter = 0; + } + } + } + + inject = ( + cls: T | Token, + ...args: Args + ) => { + return this.injectImpl(cls, args, undefined); + }; + + injectAsync = ( + cls: T | Token, + ...args: Args + ): Promise> => { + const resolutionContainer = this.resolutionContainer; + + this.loopCounter += 1; + + if (this.loopCounter > MAX_LOOP_COUNT) { + throw new AsyncCycleDependencyError(); + } + + if (this.pendingPromiseMap.has(cls)) { + return this.pendingPromiseMap.get(cls) as Promise>; + } + + if (this.instances.has(cls)) { + return Promise.resolve(this.instances.get(cls)); + } + + const promise = Promise.resolve().then(() => { + try { + return this.injectImpl(cls, args, resolutionContainer); + } finally { + this.pendingPromiseMap.delete(cls); + } + }); + + this.pendingPromiseMap.set(cls, promise); + + return promise; + }; + + register = (tokenDescriptor: TokenDescriptor) => { + const token = tokenDescriptor.token || tokenDescriptor.class; + + const descriptorWithContainer = { ...tokenDescriptor, container: this }; + + this.tokenDescriptorMap.set(token, descriptorWithContainer); + // this.tokenDescriptorMap.set(tokenDescriptor.class, descriptorWithContainer); + }; + + unregister = (token: Token | ScopedClass) => { + const descriptor = this.getTokenDescriptor(token); + + this.tokenDescriptorMap.delete(token); + + if (descriptor) { + this.instances.delete(descriptor.class); + } + }; + + reset = () => { + this.instances = new WeakMap(); + this.resolutionSet = new Set(); + this.pendingPromiseMap = new Map(); + this.resolutionContainer = null; + }; +} + +export const globalContainer = new Container(null, "Global container"); + +export const inject = globalContainer.inject; + +export const injectAsync = globalContainer.injectAsync; + +export const childContainer = globalContainer.childContainer; diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..aff7ead --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,23 @@ +export class CycleDependencyError extends Error { + constructor() { + super("Circular dependency detected"); + } +} + +export class AsyncCycleDependencyError extends Error { + constructor() { + super("Circular dependency detected in async resolution"); + } +} + +export class ArgumentsError extends Error { + constructor(scope: string, className: string) { + super(`Arguments are not supported for ${scope} of ${className}`); + } +} + +export class TokenNotRegisteredError extends Error { + constructor() { + super("Token is not registered in the container"); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..437ef8c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +export * from "./container"; +export * from "./scopes"; +export * from "./token"; +export * from "./types"; +export * from "./errors"; diff --git a/src/scopes.ts b/src/scopes.ts new file mode 100644 index 0000000..f89dbcd --- /dev/null +++ b/src/scopes.ts @@ -0,0 +1,39 @@ +import { ArgumentsError } from "./errors"; +import { globalContainer } from "./container"; +import type { ScopeHandler } from "./types"; + +export class Scopes { + public static Singleton(): ScopeHandler { + return function SingletonScope(cls, args) { + if (args.length > 0) { + throw new ArgumentsError(SingletonScope.name, cls.name); + } + + return globalContainer.getInstance(cls); + }; + } + + public static Transient(): ScopeHandler { + return function TransientScope(cls, args) { + return new cls(...args); + }; + } + + public static Container(): ScopeHandler { + return function ContainerScope(cls, args, container) { + if (args.length > 0) { + throw new ArgumentsError(ContainerScope.name, cls.name); + } + + return container.getInstance(cls); + }; + } + + public static Resolution(): ScopeHandler { + return function ResolutionScope(cls, args, _, resolutionContainer) { + return resolutionContainer.getInstance(cls, args); + }; + } + + public static Scoped = Scopes.Container; +} diff --git a/src/token.ts b/src/token.ts new file mode 100644 index 0000000..f4a2304 --- /dev/null +++ b/src/token.ts @@ -0,0 +1 @@ +export class Token any> {} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..f424ba0 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,46 @@ +import type { Container } from "./container"; +import type { Token } from "./token"; + +export type ScopeHandler = ( + cls: any, + args: any[], + container: Container, + resolutionContainer: Container +) => any; + +export interface ScopedClass { + new (...args: any[]): any; + + scope: ScopeHandler; +} + +export type Injectable< + C extends I, + I extends ScopedClass = ScopedClass +> = InstanceType; + +export type TokenDescriptor = { + token?: Token; + class: ScopedClass; + scope?: ScopeHandler; +}; + +export interface IContainer { + inject( + cls: T | Token, + ...args: Args + ): InstanceType; + + injectAsync( + cls: T | Token, + ...args: Args + ): Promise>; + + childContainer(name?: string): Container; + + register(tokenDescriptor: TokenDescriptor): void; + + unregister(token: Token | ScopedClass): void; + + reset(): void; +} diff --git a/test/dioma.test.ts b/test/dioma.test.ts new file mode 100644 index 0000000..0c5b75e --- /dev/null +++ b/test/dioma.test.ts @@ -0,0 +1,1362 @@ +import { + Container, + Scopes, + inject, + globalContainer, + CycleDependencyError, + AsyncCycleDependencyError, + injectAsync, + ArgumentsError, + Token, +} from "../src"; +import { describe, it, expect, beforeEach } from "vitest"; + +const delay = (ms: number = 0) => new Promise((resolve) => setTimeout(resolve, ms)); + +describe("Dioma", () => { + beforeEach(() => { + globalContainer.reset(); + }); + + it("should be able to create container", () => { + const container = new Container(); + + expect(container).toBeInstanceOf(Container); + }); + + describe("child container", () => { + it("should be able to create child container", () => { + const container = new Container(); + + const childContainer = container.childContainer(); + + expect(childContainer).toBeInstanceOf(Container); + }); + + it("should be able to create grandchild container", () => { + const container = new Container(); + + const childContainer = container.childContainer(); + + const grandchildContainer = childContainer.childContainer(); + + expect(grandchildContainer).toBeInstanceOf(Container); + }); + + it("should be able to inject instance from parent container", () => { + class ParentClass { + static scope = Scopes.Scoped(); + } + + const parentContainer = new Container(); + + const instance1 = parentContainer.getInstance(ParentClass); + + const childContainer = parentContainer.childContainer(); + + const instance2 = childContainer.getInstance(ParentClass); + + expect(instance1).toBeInstanceOf(ParentClass); + + expect(instance2).toBe(instance1); + }); + + it("should handle multiple child containers", () => { + class ParentClass { + static scope = Scopes.Scoped(); + } + + const parentContainer = new Container(); + + const childContainer1 = parentContainer.childContainer(); + + const childContainer2 = parentContainer.childContainer(); + + const instance1 = childContainer1.inject(ParentClass); + + const instance2 = childContainer2.inject(ParentClass); + + expect(instance1).toBeInstanceOf(ParentClass); + + expect(instance2).toBeInstanceOf(ParentClass); + + expect(instance1).not.toBe(instance2); + }); + + it("should use parent container if instance is not found in child container", () => { + class ParentClass { + static scope = Scopes.Scoped(); + } + + const parentContainer = new Container(); + + parentContainer.inject(ParentClass); + + const instance1 = parentContainer.inject(ParentClass); + + const childContainer = parentContainer.childContainer(); + + const instance2 = childContainer.inject(ParentClass); + + expect(instance1).toBeInstanceOf(ParentClass); + + expect(instance2).toBe(instance1); + }); + }); + + describe("Singleton", () => { + it("should be able to inject singleton", () => { + class SingletonClass { + static scope = Scopes.Singleton(); + } + + const singleton = inject(SingletonClass); + + expect(singleton).toBeInstanceOf(SingletonClass); + + const singleton2 = inject(SingletonClass); + + expect(singleton2).toBe(singleton); + }); + }); + + describe("Transient", () => { + it("should be able to inject transient", () => { + class TransientClass { + static scope = Scopes.Transient(); + } + + const transient = inject(TransientClass); + + expect(transient).toBeInstanceOf(TransientClass); + + const transient2 = inject(TransientClass); + + expect(transient2).not.toBe(transient); + }); + + it("uses transient scope by default", () => { + class DefaultClass {} + + const instance1 = inject(DefaultClass); + + const instance2 = inject(DefaultClass); + + expect(instance1).toBeInstanceOf(DefaultClass); + + expect(instance2).not.toBe(instance1); + }); + }); + + describe("Scoped", () => { + it("should be able to inject scoped", () => { + class ScopedClass { + static scope = Scopes.Scoped(); + } + + const container = new Container(); + + const scoped = container.inject(ScopedClass); + + const scoped2 = container.inject(ScopedClass); + + expect(scoped).toBeInstanceOf(ScopedClass); + + expect(scoped2).toBe(scoped); + }); + }); + + describe("Resolution", () => { + it("should be able to return the same instance in resolution scope", () => { + class ResolutionClassA { + constructor( + public instanceB = inject(ResolutionClassB), + public instanceC = inject(ResolutionClassC) + ) {} + + static scope = Scopes.Transient(); + } + + class ResolutionClassB { + constructor(public instanceC = inject(ResolutionClassC)) {} + + static scope = Scopes.Transient(); + } + + class ResolutionClassC { + static scope = Scopes.Resolution(); + } + + const instanceA = inject(ResolutionClassA); + + const instanceA2 = inject(ResolutionClassA); + + expect(instanceA).toBeInstanceOf(ResolutionClassA); + + expect(instanceA.instanceB).toBeInstanceOf(ResolutionClassB); + + expect(instanceA.instanceC).toBeInstanceOf(ResolutionClassC); + + expect(instanceA.instanceB.instanceC).toBeInstanceOf(ResolutionClassC); + + expect(instanceA.instanceB.instanceC).toBe(instanceA.instanceC); + + expect(instanceA2).not.toBe(instanceA); + + expect(instanceA2.instanceB.instanceC).toBe(instanceA2.instanceC); + + expect(instanceA2.instanceC).not.toBe(instanceA.instanceC); + }); + }); + + describe("Complex dependency", () => { + it("should be able to inject complex dependency", () => { + class DependencyA { + constructor(public instanceB = inject(DependencyB)) {} + + static scope = Scopes.Transient(); + } + + class DependencyB { + constructor(public instanceC = inject(DependencyC)) {} + + static scope = Scopes.Transient(); + } + + class DependencyC { + constructor(public instanceD = inject(DependencyD)) {} + + static scope = Scopes.Transient(); + } + + class DependencyD { + constructor() {} + + static scope = Scopes.Transient(); + } + + const instanceA = inject(DependencyA); + + expect(instanceA).toBeInstanceOf(DependencyA); + + expect(instanceA.instanceB).toBeInstanceOf(DependencyB); + + expect(instanceA.instanceB.instanceC).toBeInstanceOf(DependencyC); + + expect(instanceA.instanceB.instanceC.instanceD).toBeInstanceOf(DependencyD); + }); + + it("should be able to inject complex dependency with resolution scope", () => { + class DependencyZ { + constructor( + public instanceA = inject(DependencyA), + public instanceB = inject(DependencyB) + ) {} + + static scope = Scopes.Transient(); + } + + class DependencyA { + constructor( + public instanceB = inject(DependencyB), + public instanceC = inject(DependencyC) + ) {} + + static scope = Scopes.Transient(); + } + + class DependencyB { + constructor(public instanceC = inject(DependencyC)) {} + + static scope = Scopes.Transient(); + } + + class DependencyC { + constructor() {} + + static scope = Scopes.Resolution(); + } + + const instanceZ = inject(DependencyZ); + + expect(instanceZ).toBeInstanceOf(DependencyZ); + + expect(instanceZ.instanceA).toBeInstanceOf(DependencyA); + + expect(instanceZ.instanceB).toBeInstanceOf(DependencyB); + + expect(instanceZ.instanceA.instanceB).toBeInstanceOf(DependencyB); + + expect(instanceZ.instanceA.instanceC).toBeInstanceOf(DependencyC); + + expect(instanceZ.instanceB.instanceC).toBeInstanceOf(DependencyC); + + expect(instanceZ.instanceA.instanceC).toBe(instanceZ.instanceB.instanceC); + }); + }); + + describe("Circular dependency", () => { + it("should throw error when circular dependency is detected for transient scope", () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyB { + constructor(public instanceA = inject(CircularDependencyA)) {} + + static scope = Scopes.Transient(); + } + + expect(() => inject(CircularDependencyA)).toThrowError(CycleDependencyError); + }); + + it("should throw error when circular dependency is detected for container scope", () => { + const container = new Container(); + + class CircularDependencyA { + constructor(public instanceB = container.inject(CircularDependencyB)) {} + + static scope = Scopes.Scoped(); + } + + class CircularDependencyB { + constructor(public instanceA = container.inject(CircularDependencyA)) {} + + static scope = Scopes.Scoped(); + } + + expect(() => container.inject(CircularDependencyA)).toThrowError( + CycleDependencyError + ); + }); + + it("should throw error when circular dependency is detected for resolution scope", () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Resolution(); + } + + class CircularDependencyB { + constructor(public instanceA = inject(CircularDependencyA)) {} + + static scope = Scopes.Resolution(); + } + + const container = new Container(); + + expect(() => container.inject(CircularDependencyA)).toThrowError( + CycleDependencyError + ); + }); + + it("should throw error when circular dependency is detected for singleton scope", () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Singleton(); + } + + class CircularDependencyB { + constructor(public instanceA = inject(CircularDependencyA)) {} + + static scope = Scopes.Singleton(); + } + + expect(() => inject(CircularDependencyA)).toThrowError(CycleDependencyError); + }); + + it("should throw error when circular dependency is detected for multiple classes", () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyB { + constructor(public instanceC = inject(CircularDependencyC)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyC { + constructor(public instanceA = inject(CircularDependencyA)) {} + + static scope = Scopes.Transient(); + } + + expect(() => inject(CircularDependencyA)).toThrowError(CycleDependencyError); + + expect(() => inject(CircularDependencyB)).toThrowError(CycleDependencyError); + + expect(() => inject(CircularDependencyC)).toThrowError(CycleDependencyError); + }); + }); + + describe("Async", () => { + it("should be able to inject async", async () => { + class AsyncClass { + static scope = Scopes.Transient(); + } + + const instance = await injectAsync(AsyncClass); + + expect(instance).toBeInstanceOf(AsyncClass); + }); + + it("should be able to inject async for singleton scope ", async () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Singleton(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB.instanceA).toBe(instance); + + const instance2 = inject(CircularDependencyB); + + await delay(); + + expect(instance2).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance2.instanceA.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to inject async for singleton scope (both async)", async () => { + class CircularDependencyA { + public instanceB: CircularDependencyB; + + constructor(instanceBPromise = injectAsync(CircularDependencyB)) { + instanceBPromise.then((instance) => { + this.instanceB = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + class CircularDependencyB { + public instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB.instanceA).toBe(instance); + + // const instance2 = inject(CircularDependencyB); + + // await delay(); + // await delay(); + + // expect(instance2).toBeInstanceOf(CircularDependencyB); + // expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + // expect(instance2.instanceA.instanceB).toBeInstanceOf(CircularDependencyB); + // expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to inject async for singleton scope (async cycle of 3)", async () => { + class CircularDependencyA { + public instanceB: CircularDependencyB; + + constructor(instanceBPromise = injectAsync(CircularDependencyB)) { + instanceBPromise.then((instance) => { + this.instanceB = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + class CircularDependencyB { + public instanceC: CircularDependencyC; + + constructor(instanceCPromise = injectAsync(CircularDependencyC)) { + instanceCPromise.then((instance) => { + this.instanceC = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + class CircularDependencyC { + public instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Singleton(); + } + + const instance = inject(CircularDependencyA); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceC).toBeInstanceOf(CircularDependencyC); + expect(instance.instanceB.instanceC.instanceA).toBeInstanceOf(CircularDependencyA); + + expect(instance.instanceB.instanceC.instanceA).toBe(instance); + expect(instance.instanceB.instanceC.instanceA.instanceB).toBe(instance.instanceB); + expect(instance.instanceB.instanceC.instanceA.instanceB.instanceC).toBe( + instance.instanceB.instanceC + ); + }); + + it("should be able to inject async for singleton scope (star schema)", async () => { + class CircularDependencyA { + public instanceB: CircularDependencyB; + public instanceC: CircularDependencyC; + + constructor( + instanceBPromise = injectAsync(CircularDependencyB), + instanceCPromise = injectAsync(CircularDependencyC) + ) { + Promise.all([instanceBPromise, instanceCPromise]).then( + ([instanceB, instanceC]) => { + this.instanceB = instanceB; + this.instanceC = instanceC; + } + ); + } + + static scope = Scopes.Singleton(); + } + + class CircularDependencyB { + public instanceA: CircularDependencyA; + public instanceC: CircularDependencyC; + + constructor( + instanceCPromise = injectAsync(CircularDependencyC), + instanceAPromise = injectAsync(CircularDependencyA) + ) { + Promise.all([instanceCPromise, instanceAPromise]).then( + ([instanceC, instanceA]) => { + this.instanceC = instanceC; + this.instanceA = instanceA; + } + ); + } + + static scope = Scopes.Singleton(); + } + + class CircularDependencyC { + public instanceA: CircularDependencyA; + public instanceB: CircularDependencyB; + + constructor( + instanceAPromise = injectAsync(CircularDependencyA), + instanceBPromise = injectAsync(CircularDependencyB) + ) { + Promise.all([instanceAPromise, instanceBPromise]).then( + ([instanceA, instanceB]) => { + this.instanceA = instanceA; + this.instanceB = instanceB; + } + ); + } + + static scope = Scopes.Singleton(); + } + + const instance = inject(CircularDependencyA); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceC).toBeInstanceOf(CircularDependencyC); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB.instanceC).toBeInstanceOf(CircularDependencyC); + expect(instance.instanceC.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceC.instanceB).toBeInstanceOf(CircularDependencyB); + + expect(instance.instanceC.instanceA).toBe(instance); + expect(instance.instanceB.instanceA).toBe(instance); + expect(instance.instanceC.instanceB).toBe(instance.instanceB); + expect(instance.instanceB.instanceC).toBe(instance.instanceC); + }); + + it("should be able to inject async for resolution scope", async () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Resolution(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Resolution(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + await delay(); + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB.instanceA).toBe(instance); + + const instance2 = inject(CircularDependencyB); + + await delay(); + await delay(); + + expect(instance2).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance2.instanceA.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to inject async for resolution scope (both async)", async () => { + class CircularDependencyA { + public instanceB: CircularDependencyB; + + constructor(instanceBPromise = injectAsync(CircularDependencyB)) { + instanceBPromise.then((instance) => { + this.instanceB = instance; + }); + } + + static scope = Scopes.Resolution(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Resolution(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB.instanceA).toBe(instance); + + const instance2 = inject(CircularDependencyB); + + await delay(); + + expect(instance2).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance2.instanceA.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to inject async for transient scope", async () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Transient(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceA).toBeInstanceOf(CircularDependencyA); + + expect(instance.instanceB).not.toBe(instance.instanceB.instanceA.instanceB); + expect(instance.instanceB.instanceA).not.toBe(instance); + + const instance2 = inject(CircularDependencyB); + + expect(instance).not.toBe(instance2); + + await delay(); + + expect(instance2).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + expect(instance2.instanceA.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance2.instanceA.instanceB).not.toBe(instance2); + }); + + it("should be able to inject async for multiple transient classes", async () => { + class CircularDependencyA { + constructor(public instanceB = inject(CircularDependencyB)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyB { + constructor(public instanceC = inject(CircularDependencyC)) {} + + static scope = Scopes.Transient(); + } + + class CircularDependencyC { + public instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Transient(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + expect(instance.instanceB.instanceC).toBeInstanceOf(CircularDependencyC); + expect(instance.instanceB.instanceC.instanceA).toBeInstanceOf(CircularDependencyA); + + expect(instance.instanceB.instanceC.instanceA).not.toBe(instance); + expect(instance.instanceB.instanceC.instanceA.instanceB).not.toBe( + instance.instanceB + ); + expect(instance.instanceB.instanceC.instanceA.instanceB.instanceC).not.toBe( + instance.instanceB.instanceC + ); + }); + + it("should throw error when trying to inject transients with async only loop", async () => { + let errorA: Error | null = null; + let errorB: Error | null = null; + + class CircularDependencyA { + public instanceB: CircularDependencyB; + + constructor(instanceBPromise = injectAsync(CircularDependencyB)) { + instanceBPromise + .then((instance) => { + this.instanceB = instance; + }) + .catch((error) => { + errorA = error; + }); + } + + static scope = Scopes.Transient(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor(instanceAPromise = injectAsync(CircularDependencyA)) { + instanceAPromise + .then((instance) => { + this.instanceA = instance; + }) + .catch((error) => { + errorB = error; + }); + } + + static scope = Scopes.Transient(); + } + + const instance = inject(CircularDependencyA); + + await delay(); + + expect(errorA).toBeInstanceOf(AsyncCycleDependencyError); + expect(errorB).toBe(null); + }); + + it("should be able to inject async for container scope", async () => { + const container = new Container(); + + class CircularDependencyA { + constructor(public instanceB = container.inject(CircularDependencyB)) {} + + static scope = Scopes.Container(); + } + + class CircularDependencyB { + declare instanceA: CircularDependencyA; + + constructor( + public instanceAPromise = container.injectAsync(CircularDependencyA) + ) { + instanceAPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Container(); + } + + const instance = container.inject(CircularDependencyA); + + await delay(); + + expect(instance).toBeInstanceOf(CircularDependencyA); + expect(instance.instanceB).toBeInstanceOf(CircularDependencyB); + + // const instance2 = container.inject(CircularDependencyB); + + // await delay(); + // await delay(); + + // expect(instance2).toBeInstanceOf(CircularDependencyB); + // expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA); + }); + }); + + describe("Arguments", () => { + it("should be able to inject transient with arguments", () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Transient(); + } + + const instance = inject(ArgumentClass, "test"); + + expect(instance).toBeInstanceOf(ArgumentClass); + expect(instance.value).toBe("test"); + }); + + it("should be able to inject multiple transient with arguments", () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Transient(); + } + + const instance = inject(ArgumentClass, "test"); + + expect(instance).toBeInstanceOf(ArgumentClass); + expect(instance.value).toBe("test"); + + const instance2 = inject(ArgumentClass, "test2"); + + expect(instance2).toBeInstanceOf(ArgumentClass); + expect(instance2.value).toBe("test2"); + }); + + it("should be able to inject resolution with arguments", () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Resolution(); + } + + const instance = inject(ArgumentClass, "test"); + + expect(instance).toBeInstanceOf(ArgumentClass); + expect(instance.value).toBe("test"); + }); + + it("should cache first instance of resolution with arguments", () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Resolution(); + } + + class InjectClass { + constructor( + public instance1 = inject(ArgumentClass, "test"), + public instance2 = inject(ArgumentClass, "test2") + ) {} + + static scope = Scopes.Transient(); + } + + const instance = inject(InjectClass); + + expect(instance).toBeInstanceOf(InjectClass); + + expect(instance.instance1).toBeInstanceOf(ArgumentClass); + expect(instance.instance1.value).toBe("test"); + + expect(instance.instance2).toBeInstanceOf(ArgumentClass); + expect(instance.instance2.value).toBe("test"); + }); + + it("should throw error for singleton with arguments", () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Singleton(); + } + + expect(() => inject(ArgumentClass, "test")).toThrowError(ArgumentsError); + }); + + it("should throw error for container with arguments", () => { + const container = new Container(); + + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Container(); + } + + expect(() => container.inject(ArgumentClass, "test")).toThrowError(ArgumentsError); + }); + + it("should be able to inject async with arguments", async () => { + class ArgumentClass { + constructor(public value: string) {} + + static scope = Scopes.Transient(); + } + + const instance = await injectAsync(ArgumentClass, "test"); + + expect(instance).toBeInstanceOf(ArgumentClass); + expect(instance.value).toBe("test"); + }); + + it("should be able to inject async with arguments (loop)", async () => { + class ClassA { + constructor(public value: string, public instanceB = inject(ClassB, "hello")) {} + + static scope = Scopes.Resolution(); + } + + class ClassB { + public instanceA: ClassA; + + constructor(public value: string, aPromise = injectAsync(ClassA, "world")) { + aPromise.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Transient(); + } + + const instanceB = inject(ClassB, "test"); + + await delay(); + + expect(instanceB).toBeInstanceOf(ClassB); + expect(instanceB.value).toBe("test"); + + expect(instanceB.instanceA).toBeInstanceOf(ClassA); + expect(instanceB.instanceA.value).toBe("world"); + }); + }); + + describe(".register", () => { + it("should be able to register class", () => { + class RegisterClass { + constructor() {} + + static scope = Scopes.Transient(); + } + + globalContainer.register({ class: RegisterClass }); + + const instance = inject(RegisterClass); + + expect(instance).toBeInstanceOf(RegisterClass); + }); + + it("should use registration scope if provided", () => { + class RegisterClass { + constructor() {} + + static scope = Scopes.Transient(); + } + + globalContainer.register({ class: RegisterClass, scope: Scopes.Singleton() }); + + const instance = inject(RegisterClass); + const instance2 = inject(RegisterClass); + + expect(instance).toBe(instance2); + }); + + it("should use class scope if registration scope is not provided", () => { + class RegisterClass { + constructor() {} + + static scope = Scopes.Singleton(); + } + + globalContainer.register({ class: RegisterClass }); + + const instance = inject(RegisterClass); + const instance2 = inject(RegisterClass); + + expect(instance).toBe(instance2); + }); + + describe("Token injection", () => { + let container = new Container(null, "parent"); + + beforeEach(() => { + container = new Container(null, "parent"); + }); + + it("should be able to inject token", () => { + const token = new Token(); + + class TokenClass { + constructor() {} + + static scope = Scopes.Transient(); + } + + container.register({ token, class: TokenClass }); + + const instance = container.inject(token); + + expect(instance).toBeInstanceOf(TokenClass); + }); + + it("should be able to inject token with arguments", () => { + const token = new Token(); + + class TokenClass { + constructor(public value: string) {} + + static scope = Scopes.Transient(); + } + + container.register({ token, class: TokenClass }); + + const instance = container.inject(token, "test"); + + expect(instance).toBeInstanceOf(TokenClass); + expect(instance.value).toBe("test"); + }); + + it("should throw error when token is not registered", () => { + const token = new Token(); + + expect(() => container.inject(token)).toThrowError(); + }); + + it("should be able to inject token from base class", () => { + const token = new Token(); + + class TokenClass { + static scope = Scopes.Container(); + } + + container.register({ token, class: TokenClass }); + + const childContainer = container.childContainer("child"); + + const instance = childContainer.inject(token); + + const instance2 = container.inject(token); + + expect(instance).toBeInstanceOf(TokenClass); + + expect(instance2).toBe(instance); + + container.unregister(token); + + const instance3 = childContainer.inject(TokenClass); + + expect(instance3).toBeInstanceOf(TokenClass); + + expect(instance3).not.toBe(instance); + }); + + it("should be able to inject registered class", () => { + class RegisterClass { + constructor() {} + + static scope = Scopes.Container(); + } + + container.register({ class: RegisterClass }); + + const instance = container.inject(RegisterClass); + + expect(instance).toBeInstanceOf(RegisterClass); + }); + + it("should be able to inject registered class on parent container", () => { + class RegisterClass { + constructor() {} + + static scope = Scopes.Container(); + } + + container.register({ class: RegisterClass }); + + const childContainer = container.childContainer("child"); + + const instance = childContainer.inject(RegisterClass); + + expect(instance).toBeInstanceOf(RegisterClass); + + const instance2 = container.inject(RegisterClass); + + expect(instance2).toBe(instance); + + container.unregister(RegisterClass); + + const instance3 = childContainer.inject(RegisterClass); + + expect(instance3).toBeInstanceOf(RegisterClass); + + expect(instance3).not.toBe(instance); + }); + + it("should be able to inject token in async loop", async () => { + const tokenA = new Token(); + const tokenB = new Token(); + + class A { + constructor(public instanceB = container.inject(B)) {} + + static scope = Scopes.Container(); + } + + class B { + declare instanceA: A; + + constructor(promiseA = container.injectAsync(A)) { + promiseA.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Container(); + } + + container.register({ token: tokenA, class: A }); + container.register({ token: tokenB, class: B }); + + const instance = container.inject(tokenA); + + expect(instance).toBeInstanceOf(A); + + await delay(); + + expect(instance.instanceB).toBeInstanceOf(B); + expect(instance.instanceB.instanceA).toBeInstanceOf(A); + expect(instance.instanceB.instanceA).toBe(instance); + + const instance2 = container.inject(tokenB); + + expect(instance2).toBeInstanceOf(B); + + await delay(); + + expect(instance2.instanceA).toBeInstanceOf(A); + expect(instance2.instanceA.instanceB).toBeInstanceOf(B); + expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to injectAsync token in async loop", async () => { + const tokenA = new Token(); + const tokenB = new Token(); + + class A { + constructor(public instanceB = container.inject(B)) {} + + static scope = Scopes.Container(); + } + + class B { + declare instanceA: A; + + constructor(promiseA = container.injectAsync(A)) { + promiseA.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Container(); + } + + container.register({ token: tokenA, class: A }); + container.register({ token: tokenB, class: B }); + + const instance = await container.injectAsync(tokenA); + + expect(instance).toBeInstanceOf(A); + + await delay(); + + expect(instance.instanceB).toBeInstanceOf(B); + expect(instance.instanceB.instanceA).toBeInstanceOf(A); + expect(instance.instanceB.instanceA).toBe(instance); + + const instance2 = await container.injectAsync(tokenB); + + expect(instance2).toBeInstanceOf(B); + + await delay(); + + expect(instance2.instanceA).toBeInstanceOf(A); + expect(instance2.instanceA.instanceB).toBeInstanceOf(B); + expect(instance2.instanceA.instanceB).toBe(instance2); + }); + + it("should be able to inject token in async loop", async () => { + const tokenA = new Token(); + const tokenB = new Token(); + + class A { + constructor(public instanceB = container.inject(tokenB)) {} + + static scope = Scopes.Container(); + } + + class B { + declare instanceA: A; + + constructor(promiseA = container.injectAsync(tokenA)) { + promiseA.then((instance) => { + this.instanceA = instance; + }); + } + + static scope = Scopes.Container(); + } + + container.register({ token: tokenA, class: A }); + container.register({ token: tokenB, class: B }); + + const instance = container.inject(tokenA); + + expect(instance).toBeInstanceOf(A); + + await delay(); + + expect(instance.instanceB).toBeInstanceOf(B); + expect(instance.instanceB.instanceA).toBeInstanceOf(A); + expect(instance.instanceB.instanceA).toBe(instance); + }); + + it("should get the same instance of inject token and class", () => { + const token = new Token(); + + class TokenClass { + constructor() {} + + static scope = Scopes.Container(); + } + + container.register({ token, class: TokenClass }); + + const instance = container.inject(token); + const instance2 = container.inject(TokenClass); + + expect(instance).toBe(instance2); + }); + + it("should unregister token", () => { + const token = new Token(); + + class TokenClass { + constructor() {} + + static scope = Scopes.Container(); + } + + container.register({ token, class: TokenClass }); + + const instance = container.inject(token); + + expect(instance).toBeInstanceOf(TokenClass); + + container.unregister(token); + + expect(() => container.inject(token)).toThrowError(); + }); + + it("should unregister token from parent container", () => { + const token = new Token(); + + class TokenClass { + constructor() {} + + static scope = Scopes.Container(); + } + + container.register({ token, class: TokenClass }); + + const childContainer = container.childContainer("child"); + + const instance = childContainer.inject(token); + + expect(instance).toBeInstanceOf(TokenClass); + + container.unregister(token); + + expect(() => childContainer.inject(token)).toThrowError(); + }); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 9301e30..27a534c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,9 @@ { "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src", + "baseUrl": ".", "strict": true, "moduleResolution": "node", "sourceMap": true }, - "include": ["src/container.ts"], - "exclude": ["node_modules", "dist", "src/**/*.spec.ts"] + "exclude": ["node_modules", "dist", "test"] } diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..dc7854c --- /dev/null +++ b/vite.config.js @@ -0,0 +1,17 @@ +import { resolve } from "path"; +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; + +export default defineConfig({ + plugins: [dts({ rollupTypes: true })], + build: { + lib: { + // Could also be a dictionary or array of multiple entry points + entry: resolve(__dirname, "src/index.ts"), + name: "dioma", + // the proper extensions will be added + fileName: "dioma", + formats: ["umd", "es", "cjs"], + }, + }, +});