Compare commits

..

2 Commits

Author SHA1 Message Date
M1000fr
4f627eedbd chore: link with @alveojs/common from npm 2026-01-12 13:23:12 +01:00
M1000fr
baf025748a chore: initial commit for @alveojs/core 2026-01-12 12:55:42 +01:00
17 changed files with 49 additions and 283 deletions

View File

@@ -5,6 +5,7 @@
"": { "": {
"name": "core", "name": "core",
"dependencies": { "dependencies": {
"@alveojs/common": "../Common",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
}, },
"devDependencies": { "devDependencies": {
@@ -17,6 +18,8 @@
}, },
}, },
"packages": { "packages": {
"@alveojs/common": ["@alveojs/common@file:../Common", { "dependencies": { "reflect-metadata": "^0.2.2" }, "devDependencies": { "@biomejs/biome": "^2.3.11", "@types/bun": "latest" }, "peerDependencies": { "reflect-metadata": "^0.2.2", "typescript": "^5" } }],
"@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/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-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="],

View File

@@ -1,8 +1,11 @@
{ {
"name": "@alveojs/core", "name": "@alveojs/core",
"module": "index.ts", "version": "0.0.1",
"description": "Core engine for Alveo DI container",
"module": "src/index.ts",
"types": "src/index.ts",
"type": "module", "type": "module",
"private": true, "private": false,
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.11", "@biomejs/biome": "^2.3.11",
"@types/bun": "latest" "@types/bun": "latest"
@@ -11,6 +14,7 @@
"typescript": "^5" "typescript": "^5"
}, },
"dependencies": { "dependencies": {
"@alveojs/common": "^0.0.1",
"reflect-metadata": "^0.2.2" "reflect-metadata": "^0.2.2"
}, },
"scripts": { "scripts": {

View File

@@ -1,19 +1,21 @@
import "reflect-metadata"; import "reflect-metadata";
import { CircularDependencyError, ProviderNotFoundError } from "../errors";
import { ContextualModuleRef, ModuleRef } from "../module/ModuleRef";
import type { Constructor } from "../types";
import type { ForwardRefFn } from "./forwardRef";
import { import {
type BaseProvider, type BaseProvider,
CircularDependencyError,
type Constructor,
type DynamicModule, type DynamicModule,
type ForwardReference,
INJECT_METADATA_KEY, INJECT_METADATA_KEY,
INJECTABLE_METADATA_KEY, INJECTABLE_METADATA_KEY,
MODULE_METADATA_KEY, MODULE_METADATA_KEY,
type ModuleOptions, type ModuleOptions,
PARAMTYPES_METADATA_KEY,
type Provider, type Provider,
ProviderNotFoundError,
type ProviderToken, type ProviderToken,
type ResolvedProvider, type ResolvedProvider,
} from "./types"; } from "@alveojs/common";
import { ContextualModuleRef, ModuleRef } from "../module/ModuleRef";
interface ParamType { interface ParamType {
name?: string; name?: string;
@@ -64,13 +66,13 @@ export class Container {
| Constructor | Constructor
| DynamicModule | DynamicModule
| Promise<DynamicModule> | Promise<DynamicModule>
| ForwardRefFn<Constructor>, | ForwardReference<Constructor>,
): Promise<void> { ): Promise<void> {
const unwrappedModule = (await (typeof module === "object" && const unwrappedModule = (await (typeof module === "object" &&
module !== null && module !== null &&
"forwardRef" in (module as object) "forwardRef" in (module as object)
? ( ? (
module as { forwardRef: () => Constructor | DynamicModule } module as ForwardReference<Constructor | DynamicModule>
).forwardRef() ).forwardRef()
: module)) as Constructor | DynamicModule; : module)) as Constructor | DynamicModule;
@@ -104,9 +106,7 @@ export class Container {
imp !== null && imp !== null &&
"forwardRef" in (imp as object) "forwardRef" in (imp as object)
? ( ? (
imp as { imp as ForwardReference<Constructor | DynamicModule>
forwardRef: () => Constructor | DynamicModule;
}
).forwardRef() ).forwardRef()
: imp); : imp);
}), }),
@@ -290,8 +290,10 @@ export class Container {
} else if (provider.useClass) { } else if (provider.useClass) {
const targetClass = provider.useClass; const targetClass = provider.useClass;
const paramTypes: ParamType[] = const paramTypes: ParamType[] =
Reflect.getMetadata("design:paramtypes", targetClass) || Reflect.getMetadata(
[]; PARAMTYPES_METADATA_KEY,
targetClass,
) || [];
const injectTokens: ProviderToken[] = const injectTokens: ProviderToken[] =
Reflect.getMetadata(INJECT_METADATA_KEY, targetClass) || Reflect.getMetadata(INJECT_METADATA_KEY, targetClass) ||
[]; [];

View File

@@ -1,21 +0,0 @@
/**
* Interface for a function that returns a type.
*/
export type ForwardReference<T = unknown> = () => T;
/**
* Interface for the object returned by forwardRef.
*/
export interface ForwardRefFn<T = unknown> {
forwardRef: ForwardReference<T>;
}
/**
* Allows to refer to a reference which is not yet defined.
* Useful for circular dependencies between classes or modules.
*
* @param fn A function that returns the reference
*/
export function forwardRef<T>(fn: ForwardReference<T>): ForwardRefFn<T> {
return { forwardRef: fn };
}

View File

@@ -1,74 +1,21 @@
import type { Constructor, Type } from "../types"; export type {
import type { ForwardRefFn } from "./forwardRef"; BaseProvider,
ClassProvider,
DynamicModule,
ExistingProvider,
FactoryProvider,
InjectableOptions,
ModuleOptions,
Provider,
ProviderScope,
ProviderToken,
ResolvedProvider,
ValueProvider,
} from "@alveojs/common";
export type ProviderScope = "singleton" | "transient"; export {
INJECT_METADATA_KEY,
export type ProviderToken<T = unknown> = INJECTABLE_METADATA_KEY,
| Type<T> MODULE_METADATA_KEY,
| string PARAMTYPES_METADATA_KEY,
| symbol } from "@alveojs/common";
| ForwardRefFn<Type<T>>;
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 DynamicModule extends ModuleOptions {
module: Constructor;
}
export interface ModuleOptions {
imports?: (
| Constructor
| DynamicModule
| Promise<DynamicModule>
| ForwardRefFn<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";

View File

@@ -1,3 +0,0 @@
export * from "./inject.decorator";
export * from "./injectable.decorator";
export * from "./module.decorator";

View File

@@ -1,22 +0,0 @@
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);
};
}

View File

@@ -1,16 +0,0 @@
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);
};
}

View File

@@ -1,14 +0,0 @@
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);
};
}

View File

@@ -1,47 +0,0 @@
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}`);
}
}

View File

@@ -1,5 +1,5 @@
import type { ProviderToken } from "@alveojs/common";
import type { Container } from "../container/Container"; import type { Container } from "../container/Container";
import type { ProviderToken } from "../container/types";
/** /**
* AlveoApplication is the main class representing an Alveo application instance. * AlveoApplication is the main class representing an Alveo application instance.

View File

@@ -1,6 +1,5 @@
import type { Constructor, DynamicModule } from "@alveojs/common";
import { Container } from "../container/Container"; import { Container } from "../container/Container";
import type { DynamicModule } from "../container/types";
import type { Constructor } from "../types";
import { AlveoApplication } from "./AlveoApplication"; import { AlveoApplication } from "./AlveoApplication";
/** /**

View File

@@ -1,21 +1,5 @@
export * from "@alveojs/common";
export { Container } from "./container/Container"; export { Container } from "./container/Container";
export type { ForwardReference, ForwardRefFn } from "./container/forwardRef";
export { forwardRef } from "./container/forwardRef";
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 { AlveoApplication } from "./factory/AlveoApplication";
export { AlveoFactory } from "./factory/AlveoFactory"; export { AlveoFactory } from "./factory/AlveoFactory";
export * from "./lifecycle";
export { ModuleRef } from "./module/ModuleRef"; export { ModuleRef } from "./module/ModuleRef";
export type { Abstract, Constructor, Type } from "./types";

View File

@@ -1,37 +0,0 @@
/**
* 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 once all modules have been initialized,
* but before the application starts listening for connections.
*/
export interface OnApplicationBootstrap {
onApplicationBootstrap(): 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>;
}
/**
* Interface defining a lifecycle hook that is called after all onModuleDestroy() handlers have completed.
*/
export interface BeforeApplicationShutdown {
beforeApplicationShutdown(signal?: string): void | Promise<void>;
}
/**
* Interface defining a lifecycle hook that is called after connections close (app.close() resolves).
*/
export interface OnApplicationShutdown {
onApplicationShutdown(signal?: string): void | Promise<void>;
}

View File

@@ -1,5 +1,5 @@
import type { ProviderToken } from "@alveojs/common";
import type { Container } from "../container/Container"; import type { Container } from "../container/Container";
import type { ProviderToken } from "../container/types";
/** /**
* ModuleRef is an abstraction that allows for dynamic resolution of providers. * ModuleRef is an abstraction that allows for dynamic resolution of providers.

View File

@@ -1,10 +0,0 @@
// 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>;

View File

@@ -1,6 +1,5 @@
{ {
"compilerOptions": { "compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"], "lib": ["ESNext"],
"target": "ESNext", "target": "ESNext",
"module": "Preserve", "module": "Preserve",
@@ -10,22 +9,20 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
// Bundler mode
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noEmit": true, "noEmit": true,
// Best practices
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"noImplicitOverride": true, "noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false "noPropertyAccessFromIndexSignature": false
} },
"include": ["src/**/*", "test/**/*", "index.ts"]
} }