feat: init project
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
22
.zed/settings.json
Normal file
22
.zed/settings.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Folder-specific settings
|
||||||
|
//
|
||||||
|
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||||
|
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||||
|
{
|
||||||
|
"formatter": { "language_server": { "name": "biome" } },
|
||||||
|
"code_actions_on_format": {
|
||||||
|
"source.fixAll.biome": true,
|
||||||
|
"source.organizeImports.biome": true
|
||||||
|
},
|
||||||
|
"lsp": {
|
||||||
|
"biome": {
|
||||||
|
"settings": {
|
||||||
|
"require_config_file": true
|
||||||
|
},
|
||||||
|
"binary": {
|
||||||
|
"path": "node_modules/.bin/biome",
|
||||||
|
"arguments": ["lsp-proxy"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
README.md
10
README.md
@@ -1,4 +1,6 @@
|
|||||||
# core
|
# Alveo
|
||||||
|
|
||||||
|
Alveo is a lightweight example of DI (Dependency Injection) container for TypeScript and JavaScript projects, designed to be simple and easy to use.
|
||||||
|
|
||||||
To install dependencies:
|
To install dependencies:
|
||||||
|
|
||||||
@@ -6,10 +8,8 @@ To install dependencies:
|
|||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
To run:
|
To run test:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run index.ts
|
bun test.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
|
||||||
|
|||||||
68
biome.json
Normal file
68
biome.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": true,
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"indentWidth": 4,
|
||||||
|
"lineEnding": "lf",
|
||||||
|
"lineWidth": 80,
|
||||||
|
"attributePosition": "auto"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"complexity": {
|
||||||
|
"noStaticOnlyClass": "off",
|
||||||
|
"noBannedTypes": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedVariables": "error",
|
||||||
|
"noUnusedPrivateClassMembers": "error",
|
||||||
|
"noUnusedFunctionParameters": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"useImportType": "off",
|
||||||
|
"useNodejsImportProtocol": "off",
|
||||||
|
"useTemplate": "off",
|
||||||
|
"noNonNullAssertion": "off",
|
||||||
|
"useLiteralEnumMembers": "off"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "error",
|
||||||
|
"noImplicitAnyLet": "off",
|
||||||
|
"noAssignInExpressions": "off",
|
||||||
|
"useIterableCallbackReturn": "off",
|
||||||
|
"noShadowRestrictedNames": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double"
|
||||||
|
},
|
||||||
|
"parser": {
|
||||||
|
"unsafeParameterDecoratorsEnabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["dist/**", "node_modules/**"],
|
||||||
|
"linter": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
bun.lock
Normal file
50
bun.lock
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "core",
|
||||||
|
"dependencies": {
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^2.3.11",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@biomejs/biome": ["@biomejs/biome@2.3.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.11", "@biomejs/cli-darwin-x64": "2.3.11", "@biomejs/cli-linux-arm64": "2.3.11", "@biomejs/cli-linux-arm64-musl": "2.3.11", "@biomejs/cli-linux-x64": "2.3.11", "@biomejs/cli-linux-x64-musl": "2.3.11", "@biomejs/cli-win32-arm64": "2.3.11", "@biomejs/cli-win32-x64": "2.3.11" }, "bin": { "biome": "bin/biome" } }, "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw=="],
|
||||||
|
|
||||||
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.11", "", { "os": "win32", "cpu": "x64" }, "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||||
|
|
||||||
|
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "@alveojs/core",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^2.3.11",
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"reflect-metadata": "^0.2.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"format": "biome format",
|
||||||
|
"lint": "biome lint",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
339
src/container/Container.ts
Normal file
339
src/container/Container.ts
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import { CircularDependencyError, ProviderNotFoundError } from "../errors";
|
||||||
|
import { ContextualModuleRef, ModuleRef } from "../module/ModuleRef";
|
||||||
|
import type { Constructor } from "../types";
|
||||||
|
import {
|
||||||
|
type BaseProvider,
|
||||||
|
INJECT_METADATA_KEY,
|
||||||
|
INJECTABLE_METADATA_KEY,
|
||||||
|
MODULE_METADATA_KEY,
|
||||||
|
type ModuleOptions,
|
||||||
|
type Provider,
|
||||||
|
type ProviderToken,
|
||||||
|
type ResolvedProvider,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
interface ParamType {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type LifecycleHook = "onModuleInit" | "onModuleDestroy";
|
||||||
|
type LifecycleHookFn = () => Promise<void> | void;
|
||||||
|
type LifecycleAware = Partial<Record<LifecycleHook, LifecycleHookFn>>;
|
||||||
|
|
||||||
|
export class Container {
|
||||||
|
private readonly providers = new Map<
|
||||||
|
string,
|
||||||
|
Map<ProviderToken, ResolvedProvider>
|
||||||
|
>();
|
||||||
|
private readonly instances = new Map<string, Map<ProviderToken, unknown>>();
|
||||||
|
private readonly moduleOptions = new Map<string, ModuleOptions>();
|
||||||
|
private readonly resolving = new Set<ProviderToken>();
|
||||||
|
private rootModuleName?: string;
|
||||||
|
|
||||||
|
constructor(private readonly globalContext = "global") {
|
||||||
|
this.ensureScope(this.globalContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerRootModule(moduleClass: Constructor): void {
|
||||||
|
this.rootModuleName = moduleClass.name;
|
||||||
|
this.registerModule(moduleClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRootModuleName(): string | undefined {
|
||||||
|
return this.rootModuleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerModule(moduleClass: Constructor): void {
|
||||||
|
const options: ModuleOptions | undefined = Reflect.getMetadata(
|
||||||
|
MODULE_METADATA_KEY,
|
||||||
|
moduleClass,
|
||||||
|
);
|
||||||
|
if (!options) return;
|
||||||
|
|
||||||
|
const moduleName = moduleClass.name;
|
||||||
|
if (this.moduleOptions.has(moduleName)) return;
|
||||||
|
|
||||||
|
this.moduleOptions.set(moduleName, options);
|
||||||
|
this.ensureScope(moduleName);
|
||||||
|
|
||||||
|
if (options.imports) {
|
||||||
|
for (const imported of options.imports) {
|
||||||
|
this.registerModule(imported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.providers) {
|
||||||
|
for (const provider of options.providers) {
|
||||||
|
const normalized = this.normalizeProvider(provider, moduleName);
|
||||||
|
this.providers
|
||||||
|
.get(moduleName)!
|
||||||
|
.set(normalized.token, normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleProvider = this.normalizeProvider(moduleClass, moduleName);
|
||||||
|
this.providers.get(moduleName)!.set(moduleClass, moduleProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerProvider(provider: Provider): void {
|
||||||
|
const normalized = this.normalizeProvider(provider, this.globalContext);
|
||||||
|
this.providers
|
||||||
|
.get(this.globalContext)!
|
||||||
|
.set(normalized.token, normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get<T>(
|
||||||
|
token: ProviderToken<T>,
|
||||||
|
moduleName?: string,
|
||||||
|
): Promise<T> {
|
||||||
|
return this.resolve(token, moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resolveAll(): Promise<void> {
|
||||||
|
for (const [scope] of this.providers) {
|
||||||
|
for (const token of this.providers.get(scope)!.keys()) {
|
||||||
|
await this.resolve(
|
||||||
|
token,
|
||||||
|
scope === this.globalContext ? undefined : scope,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async callLifecycleHook(
|
||||||
|
hook: "onModuleInit" | "onModuleDestroy",
|
||||||
|
): Promise<void> {
|
||||||
|
for (const instances of this.instances.values()) {
|
||||||
|
for (const instance of instances.values()) {
|
||||||
|
if (!instance) continue;
|
||||||
|
const lifecycleInstance = instance as LifecycleAware;
|
||||||
|
const hookFn = lifecycleInstance[hook];
|
||||||
|
if (typeof hookFn === "function") {
|
||||||
|
await hookFn.call(lifecycleInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolve<T>(
|
||||||
|
token: ProviderToken<T>,
|
||||||
|
moduleName?: string,
|
||||||
|
): Promise<T> {
|
||||||
|
const context = moduleName ?? this.globalContext;
|
||||||
|
|
||||||
|
if (token === ModuleRef) {
|
||||||
|
return new ContextualModuleRef(this, context) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
let provider = this.findProvider(token, context);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!provider &&
|
||||||
|
context === this.globalContext &&
|
||||||
|
this.rootModuleName
|
||||||
|
) {
|
||||||
|
provider = this.findProvider(token, this.rootModuleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
throw new ProviderNotFoundError(
|
||||||
|
typeof token === "function" ? token.name : String(token),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.scope === "singleton") {
|
||||||
|
const existing = this.instances
|
||||||
|
.get(provider.moduleName ?? context)
|
||||||
|
?.get(provider.token);
|
||||||
|
if (existing !== undefined) {
|
||||||
|
return existing as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.resolving.has(token)) {
|
||||||
|
const stack = [...this.resolving].map((item) =>
|
||||||
|
typeof item === "function" ? item.name : String(item),
|
||||||
|
);
|
||||||
|
stack.push(
|
||||||
|
typeof token === "function" ? token.name : String(token),
|
||||||
|
);
|
||||||
|
throw new CircularDependencyError(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resolving.add(token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let instance: T;
|
||||||
|
|
||||||
|
if (provider.useValue !== undefined) {
|
||||||
|
instance = provider.useValue as T;
|
||||||
|
} else if (provider.useFactory) {
|
||||||
|
const dependencies = await Promise.all(
|
||||||
|
(provider.inject ?? []).map((dep) =>
|
||||||
|
this.resolve(dep, provider.moduleName ?? context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
instance = (await provider.useFactory(...dependencies)) as T;
|
||||||
|
} else if (provider.useExisting) {
|
||||||
|
instance = await this.resolve<T>(
|
||||||
|
provider.useExisting as ProviderToken<T>,
|
||||||
|
provider.moduleName ?? context,
|
||||||
|
);
|
||||||
|
} else if (provider.useClass) {
|
||||||
|
const targetClass = provider.useClass;
|
||||||
|
const paramTypes: ParamType[] =
|
||||||
|
Reflect.getMetadata("design:paramtypes", targetClass) || [];
|
||||||
|
const injectTokens: ProviderToken[] =
|
||||||
|
Reflect.getMetadata(INJECT_METADATA_KEY, targetClass) || [];
|
||||||
|
|
||||||
|
this.debug(`[Container] Instantiating ${targetClass.name}`);
|
||||||
|
this.debug(
|
||||||
|
"[Container] ParamTypes:",
|
||||||
|
paramTypes.map((p) => p?.name || String(p)),
|
||||||
|
);
|
||||||
|
this.debug(
|
||||||
|
"[Container] InjectTokens:",
|
||||||
|
injectTokens.map((token) =>
|
||||||
|
token ? String(token) : "undefined",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const dependencies = await Promise.all(
|
||||||
|
paramTypes.map(async (paramType, index) => {
|
||||||
|
const depToken =
|
||||||
|
injectTokens[index] || (paramType as ProviderToken);
|
||||||
|
this.debug(
|
||||||
|
`[Container] Resolving dependency ${index}: ${String(depToken)}`,
|
||||||
|
);
|
||||||
|
return this.resolve(
|
||||||
|
depToken as ProviderToken<unknown>,
|
||||||
|
provider.moduleName ?? context,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = new targetClass(...dependencies) as T;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid provider configuration for token ${String(token)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.scope === "singleton") {
|
||||||
|
const scopeName = provider.moduleName ?? context;
|
||||||
|
this.ensureScope(scopeName);
|
||||||
|
this.instances.get(scopeName)!.set(provider.token, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
} finally {
|
||||||
|
this.resolving.delete(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findProvider(
|
||||||
|
token: ProviderToken,
|
||||||
|
context: string,
|
||||||
|
): ResolvedProvider | undefined {
|
||||||
|
const moduleProviders = this.providers.get(context);
|
||||||
|
if (moduleProviders?.has(token)) {
|
||||||
|
return moduleProviders.get(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = this.moduleOptions.get(context);
|
||||||
|
if (options?.imports) {
|
||||||
|
for (const imported of options.imports) {
|
||||||
|
const importedProviders = this.providers.get(imported.name);
|
||||||
|
const importedOptions = this.moduleOptions.get(imported.name);
|
||||||
|
if (
|
||||||
|
importedProviders?.has(token) &&
|
||||||
|
importedOptions?.exports?.includes(token)
|
||||||
|
) {
|
||||||
|
return importedProviders.get(token);
|
||||||
|
}
|
||||||
|
if (token === imported) {
|
||||||
|
return importedProviders?.get(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context !== this.globalContext) {
|
||||||
|
const globalProviders = this.providers.get(this.globalContext);
|
||||||
|
if (globalProviders?.has(token)) {
|
||||||
|
return globalProviders.get(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context === this.globalContext && this.rootModuleName) {
|
||||||
|
const rootProviders = this.providers.get(this.rootModuleName);
|
||||||
|
if (rootProviders?.has(token)) {
|
||||||
|
return rootProviders.get(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeProvider(
|
||||||
|
provider: Provider,
|
||||||
|
moduleName?: string,
|
||||||
|
): ResolvedProvider {
|
||||||
|
if (typeof provider === "function") {
|
||||||
|
const metadata = Reflect.getMetadata(
|
||||||
|
INJECTABLE_METADATA_KEY,
|
||||||
|
provider,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
token: provider,
|
||||||
|
scope: metadata?.scope ?? "singleton",
|
||||||
|
useClass: provider,
|
||||||
|
moduleName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
token: provider.provide,
|
||||||
|
scope: provider.scope ?? "singleton",
|
||||||
|
moduleName,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ("useClass" in provider) {
|
||||||
|
return { ...base, useClass: provider.useClass };
|
||||||
|
}
|
||||||
|
if ("useValue" in provider) {
|
||||||
|
return { ...base, useValue: provider.useValue };
|
||||||
|
}
|
||||||
|
if ("useFactory" in provider) {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
useFactory: provider.useFactory,
|
||||||
|
inject: provider.inject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("useExisting" in provider) {
|
||||||
|
return { ...base, useExisting: provider.useExisting };
|
||||||
|
}
|
||||||
|
|
||||||
|
const exhaustiveCheck: never = provider;
|
||||||
|
throw new Error(
|
||||||
|
`Invalid provider definition for token ${String((exhaustiveCheck as BaseProvider).provide)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureScope(name: string): void {
|
||||||
|
if (!this.providers.has(name)) {
|
||||||
|
this.providers.set(name, new Map());
|
||||||
|
}
|
||||||
|
if (!this.instances.has(name)) {
|
||||||
|
this.instances.set(name, new Map());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private debug(...args: unknown[]): void {
|
||||||
|
if (process.env.ALVEO_CONTAINER_DEBUG === "true") {
|
||||||
|
console.debug(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/container/types.ts
Normal file
60
src/container/types.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { Constructor, Type } from "../types";
|
||||||
|
|
||||||
|
export type ProviderScope = "singleton" | "transient";
|
||||||
|
|
||||||
|
export type ProviderToken<T = unknown> = Type<T> | string | symbol;
|
||||||
|
|
||||||
|
export interface BaseProvider<T = unknown> {
|
||||||
|
provide: ProviderToken<T>;
|
||||||
|
scope?: ProviderScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClassProvider<T = unknown> extends BaseProvider<T> {
|
||||||
|
useClass: Constructor<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValueProvider<T = unknown> extends BaseProvider<T> {
|
||||||
|
useValue: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FactoryProvider<T = unknown> extends BaseProvider<T> {
|
||||||
|
useFactory: (...args: unknown[]) => T | Promise<T>;
|
||||||
|
inject?: ProviderToken[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExistingProvider<T = unknown> extends BaseProvider<T> {
|
||||||
|
useExisting: ProviderToken<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Provider<T = unknown> =
|
||||||
|
| Constructor<T>
|
||||||
|
| ClassProvider<T>
|
||||||
|
| ValueProvider<T>
|
||||||
|
| FactoryProvider<T>
|
||||||
|
| ExistingProvider<T>;
|
||||||
|
|
||||||
|
export interface ResolvedProvider<T = unknown> {
|
||||||
|
token: ProviderToken<T>;
|
||||||
|
scope: ProviderScope;
|
||||||
|
useClass?: Constructor<T>;
|
||||||
|
useValue?: T;
|
||||||
|
useFactory?: (...args: unknown[]) => T | Promise<T>;
|
||||||
|
useExisting?: ProviderToken<T>;
|
||||||
|
inject?: ProviderToken[];
|
||||||
|
moduleName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InjectableOptions {
|
||||||
|
scope?: ProviderScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModuleOptions {
|
||||||
|
imports?: Constructor[];
|
||||||
|
providers?: Provider[];
|
||||||
|
exports?: ProviderToken[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INJECTABLE_METADATA_KEY = "alveo:injectable";
|
||||||
|
export const INJECT_METADATA_KEY = "alveo:inject";
|
||||||
|
export const MODULE_METADATA_KEY = "alveo:module";
|
||||||
|
export const PARAMTYPES_METADATA_KEY = "design:paramtypes";
|
||||||
3
src/decorators/index.ts
Normal file
3
src/decorators/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./inject.decorator";
|
||||||
|
export * from "./injectable.decorator";
|
||||||
|
export * from "./module.decorator";
|
||||||
22
src/decorators/inject.decorator.ts
Normal file
22
src/decorators/inject.decorator.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import { INJECT_METADATA_KEY, type ProviderToken } from "../container/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that explicitly specifies a token for dependency injection.
|
||||||
|
* Useful when injecting interfaces (via symbols or strings) or when multiple
|
||||||
|
* providers are available for the same type.
|
||||||
|
*
|
||||||
|
* @param token The token to inject
|
||||||
|
*/
|
||||||
|
export function Inject(token: ProviderToken): ParameterDecorator {
|
||||||
|
return (
|
||||||
|
target: object,
|
||||||
|
_propertyKey: string | symbol | undefined,
|
||||||
|
parameterIndex: number,
|
||||||
|
) => {
|
||||||
|
const existingInjections =
|
||||||
|
Reflect.getMetadata(INJECT_METADATA_KEY, target) || [];
|
||||||
|
existingInjections[parameterIndex] = token;
|
||||||
|
Reflect.defineMetadata(INJECT_METADATA_KEY, existingInjections, target);
|
||||||
|
};
|
||||||
|
}
|
||||||
16
src/decorators/injectable.decorator.ts
Normal file
16
src/decorators/injectable.decorator.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import {
|
||||||
|
INJECTABLE_METADATA_KEY,
|
||||||
|
type InjectableOptions,
|
||||||
|
} from "../container/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that marks a class as available to be provided and injected as a dependency.
|
||||||
|
*
|
||||||
|
* @param options Options for the injectable (e.g., scope)
|
||||||
|
*/
|
||||||
|
export function Injectable(options?: InjectableOptions): ClassDecorator {
|
||||||
|
return (target: object) => {
|
||||||
|
Reflect.defineMetadata(INJECTABLE_METADATA_KEY, options || {}, target);
|
||||||
|
};
|
||||||
|
}
|
||||||
14
src/decorators/module.decorator.ts
Normal file
14
src/decorators/module.decorator.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import { MODULE_METADATA_KEY, type ModuleOptions } from "../container/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that marks a class as an Alveo module.
|
||||||
|
* Modules are used to organize the application structure and define providers, imports, and exports.
|
||||||
|
*
|
||||||
|
* @param options Module configuration options
|
||||||
|
*/
|
||||||
|
export function Module(options: ModuleOptions): ClassDecorator {
|
||||||
|
return (target: object) => {
|
||||||
|
Reflect.defineMetadata(MODULE_METADATA_KEY, options, target);
|
||||||
|
};
|
||||||
|
}
|
||||||
47
src/errors/index.ts
Normal file
47
src/errors/index.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export class AlveoError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProviderNotFoundError extends AlveoError {
|
||||||
|
constructor(token: string, context?: string) {
|
||||||
|
const contextMsg = context ? ` in the "${context}" context` : "";
|
||||||
|
super(
|
||||||
|
`Alveo can't resolve dependencies of the ${token}${contextMsg}. Please make sure that it is available in the current module scope.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CircularDependencyError extends AlveoError {
|
||||||
|
constructor(stack: string[]) {
|
||||||
|
super(`Circular dependency detected: ${stack.join(" -> ")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidModuleError extends AlveoError {
|
||||||
|
constructor(target: string) {
|
||||||
|
super(
|
||||||
|
`The class "${target}" is not a valid Alveo module. Did you forget the @Module() decorator?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidProviderError extends AlveoError {
|
||||||
|
constructor(provider: unknown) {
|
||||||
|
super(`Invalid provider definition: ${JSON.stringify(provider)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LifecycleError extends AlveoError {
|
||||||
|
constructor(hook: string, error: Error) {
|
||||||
|
super(`Error during lifecycle hook "${hook}": ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BootstrapError extends AlveoError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(`Application bootstrap failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/factory/AlveoApplication.ts
Normal file
36
src/factory/AlveoApplication.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { Container } from "../container/Container";
|
||||||
|
import type { ProviderToken } from "../container/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlveoApplication is the main class representing an Alveo application instance.
|
||||||
|
* It provides methods to interact with the dependency injection container and manage the application lifecycle.
|
||||||
|
*/
|
||||||
|
export class AlveoApplication {
|
||||||
|
constructor(private readonly container: Container) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an instance of a provider from the application container.
|
||||||
|
*
|
||||||
|
* @param token The provider token (Class, String, or Symbol)
|
||||||
|
* @returns A promise resolving to the instance of the provider
|
||||||
|
*/
|
||||||
|
public async get<T>(token: ProviderToken<T>): Promise<T> {
|
||||||
|
return this.container.get(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the application by calling onModuleInit hooks on all providers.
|
||||||
|
* This is called automatically by AlveoFactory.create().
|
||||||
|
*/
|
||||||
|
public async init(): Promise<this> {
|
||||||
|
await this.container.callLifecycleHook("onModuleInit");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gracefully shuts down the application by calling onModuleDestroy hooks.
|
||||||
|
*/
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
await this.container.callLifecycleHook("onModuleDestroy");
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/factory/AlveoFactory.ts
Normal file
38
src/factory/AlveoFactory.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Container } from "../container/Container";
|
||||||
|
import type { Constructor } from "../types";
|
||||||
|
import { AlveoApplication } from "./AlveoApplication";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlveoFactory is the entry point for creating Alveo application instances.
|
||||||
|
* It handles the creation of the dependency injection container and module registration.
|
||||||
|
*/
|
||||||
|
export class AlveoFactoryStatic {
|
||||||
|
/**
|
||||||
|
* Creates an instance of an Alveo application.
|
||||||
|
*
|
||||||
|
* @param rootModule The root module of the application.
|
||||||
|
* @returns A promise that resolves to an AlveoApplication instance.
|
||||||
|
*/
|
||||||
|
public async create(rootModule: Constructor): Promise<AlveoApplication> {
|
||||||
|
const container = new Container();
|
||||||
|
|
||||||
|
// 1. Register the module tree starting from the root module
|
||||||
|
container.registerRootModule(rootModule);
|
||||||
|
|
||||||
|
// 2. Resolve all providers (this builds the graph and instantiates singletons)
|
||||||
|
await container.resolveAll();
|
||||||
|
|
||||||
|
// 3. Wrap in the application instance
|
||||||
|
const app = new AlveoApplication(container);
|
||||||
|
|
||||||
|
// 4. Initialize lifecycle hooks
|
||||||
|
await app.init();
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static instance of AlveoFactory to be used by consumers.
|
||||||
|
*/
|
||||||
|
export const AlveoFactory = new AlveoFactoryStatic();
|
||||||
19
src/index.ts
Normal file
19
src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export { Container } from "./container/Container";
|
||||||
|
export type {
|
||||||
|
ClassProvider,
|
||||||
|
ExistingProvider,
|
||||||
|
FactoryProvider,
|
||||||
|
InjectableOptions,
|
||||||
|
ModuleOptions,
|
||||||
|
Provider,
|
||||||
|
ProviderScope as Scope,
|
||||||
|
ProviderToken,
|
||||||
|
ValueProvider,
|
||||||
|
} from "./container/types";
|
||||||
|
export * from "./decorators";
|
||||||
|
export * from "./errors";
|
||||||
|
export { AlveoApplication } from "./factory/AlveoApplication";
|
||||||
|
export { AlveoFactory } from "./factory/AlveoFactory";
|
||||||
|
export * from "./lifecycle";
|
||||||
|
export { ModuleRef } from "./module/ModuleRef";
|
||||||
|
export type { Abstract, Constructor, Type } from "./types";
|
||||||
15
src/lifecycle/index.ts
Normal file
15
src/lifecycle/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Interface defining a lifecycle hook that is called once the module has been initialized.
|
||||||
|
* All dependencies have been resolved and instantiated at this point.
|
||||||
|
*/
|
||||||
|
export interface OnModuleInit {
|
||||||
|
onModuleInit(): void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining a lifecycle hook that is called when the application is shutting down.
|
||||||
|
* Useful for cleanup logic like closing database connections or stopping intervals.
|
||||||
|
*/
|
||||||
|
export interface OnModuleDestroy {
|
||||||
|
onModuleDestroy(): void | Promise<void>;
|
||||||
|
}
|
||||||
37
src/module/ModuleRef.ts
Normal file
37
src/module/ModuleRef.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { Container } from "../container/Container";
|
||||||
|
import type { ProviderToken } from "../container/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModuleRef is an abstraction that allows for dynamic resolution of providers.
|
||||||
|
* It is typically injected into services or controllers when dynamic lookup is needed.
|
||||||
|
*/
|
||||||
|
export abstract class ModuleRef {
|
||||||
|
constructor(protected readonly container: Container) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an instance of a provider based on its token.
|
||||||
|
*
|
||||||
|
* @param token The token of the provider to retrieve.
|
||||||
|
* @returns A promise that resolves to the provider instance.
|
||||||
|
*/
|
||||||
|
public abstract get<T>(token: ProviderToken<T>): Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contextual implementation of ModuleRef that resolves providers within a specific module's scope.
|
||||||
|
*/
|
||||||
|
export class ContextualModuleRef extends ModuleRef {
|
||||||
|
constructor(
|
||||||
|
container: Container,
|
||||||
|
private readonly moduleName: string,
|
||||||
|
) {
|
||||||
|
super(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the token within the context of the module this Ref belongs to.
|
||||||
|
*/
|
||||||
|
public override async get<T>(token: ProviderToken<T>): Promise<T> {
|
||||||
|
return this.container.get(token, this.moduleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/types.ts
Normal file
10
src/types.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Required for constructor variance compatibility
|
||||||
|
export type ConstructorArgs = any[];
|
||||||
|
|
||||||
|
export type Constructor<T = unknown> = new (...args: ConstructorArgs) => T;
|
||||||
|
|
||||||
|
export type Abstract<T = unknown> = abstract new (
|
||||||
|
...args: ConstructorArgs
|
||||||
|
) => T;
|
||||||
|
|
||||||
|
export type Type<T = unknown> = Constructor<T> | Abstract<T>;
|
||||||
69
test.ts
Normal file
69
test.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import "reflect-metadata";
|
||||||
|
import {
|
||||||
|
AlveoFactory,
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
Module,
|
||||||
|
ModuleRef,
|
||||||
|
type OnModuleInit,
|
||||||
|
} from "./index";
|
||||||
|
|
||||||
|
const LOGGER_TOKEN = Symbol("LOGGER");
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class LoggerService {
|
||||||
|
log(message: string) {
|
||||||
|
console.log(`[Logger]: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class DatabaseService implements OnModuleInit {
|
||||||
|
constructor(
|
||||||
|
@Inject(LOGGER_TOKEN) private readonly logger: LoggerService,
|
||||||
|
@Inject(ModuleRef) private readonly moduleRef: ModuleRef,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
this.logger.log("DatabaseService initialized.");
|
||||||
|
const self = await this.moduleRef.get(DatabaseService);
|
||||||
|
this.logger.log(`ModuleRef self-check: ${self === this}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
query() {
|
||||||
|
return "data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TestService {
|
||||||
|
constructor(private readonly logger: LoggerService) {
|
||||||
|
this.logger.log("TestService initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
DatabaseService,
|
||||||
|
LoggerService,
|
||||||
|
{ provide: LOGGER_TOKEN, useExisting: LoggerService },
|
||||||
|
],
|
||||||
|
exports: [DatabaseService, LoggerService],
|
||||||
|
})
|
||||||
|
class DatabaseModule {}
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [DatabaseModule],
|
||||||
|
providers: [TestService],
|
||||||
|
})
|
||||||
|
class AppModule {}
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
console.log("Starting AlveoJS test...");
|
||||||
|
const app = await AlveoFactory.create(AppModule);
|
||||||
|
const db = await app.get(DatabaseService);
|
||||||
|
console.log(`Query result: ${db.query()}`);
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap().catch(console.error);
|
||||||
31
tsconfig.json
Normal file
31
tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user