docs: enhance type definitions and decorators with detailed JSDoc comments

This commit is contained in:
Stevan Freeborn
2025-05-12 22:23:31 -05:00
parent 8a16113de3
commit 586ee228ac
4 changed files with 358 additions and 0 deletions
+38
View File
@@ -1,8 +1,33 @@
import type { ServiceIdentifier } from './types.js';
/**
* Metadata key for storing parameter type information for dependency injection
* @internal
*/
export const DI_PARAM_TYPES = 'di:paramtypes';
/**
* Metadata key for marking classes as injectable
* @internal
*/
export const DI_INJECTABLE = 'di:injectable';
/**
* Decorator for constructor parameters that specifies which service identifier to use for injection
*
* @template T - The type of the service to be injected
* @param serviceType - The service identifier for the dependency to inject
* @returns A parameter decorator function that associates the parameter with the service identifier
*
* @example
* ```typescript
* class MyService {
* constructor(
* @inject(loggerIdentifier) private logger: ILogger
* ) {}
* }
* ```
*/
export function inject<T>(serviceType: ServiceIdentifier<T>): ParameterDecorator {
// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
return (target: Object, _: string | symbol | undefined, parameterIndex: number) => {
@@ -10,6 +35,19 @@ export function inject<T>(serviceType: ServiceIdentifier<T>): ParameterDecorator
};
}
/**
* Decorator that marks a class as injectable, allowing the container to create instances with dependencies
*
* @returns A class decorator function that marks the class as injectable
*
* @example
* ```typescript
* @injectable()
* class MyService {
* constructor() {}
* }
* ```
*/
export function injectable(): ClassDecorator {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
return (target: Function) => {
+83
View File
@@ -9,10 +9,38 @@ import type {
ServiceLifetime,
} from './types.js';
/**
* A collection of service descriptors that can be used to build a service provider.
*
* This class is used to register services with different lifetimes and build a service provider
* that can resolve those services at runtime.
*
* @implements {IServiceCollection}
*/
export class ServiceCollection implements IServiceCollection {
/**
* Internal map of service descriptors, keyed by service identifiers
*/
private readonly _descriptors: Map<ServiceIdentifier<unknown>, ServiceDescriptor<unknown>> =
new Map();
/**
* Registers a singleton service with the collection.
*
* Singleton services are created once and shared by all consumers.
*
* @template T - The type of the service to register
* @param serviceType - The service identifier
* @param implementationOrFactory - The implementation class or factory function
* @returns The service collection instance for method chaining
*
* @example
* ```typescript
* services.addSingleton(userServiceIdentifier, UserService);
* // or with a factory:
* services.addSingleton(userServiceIdentifier, (provider) => new UserService(provider.getService(loggerIdentifier)));
* ```
*/
public addSingleton<T>(
serviceType: ServiceIdentifier<T>,
implementationOrFactory: ServiceFactory<T> | Constructor<T>,
@@ -20,6 +48,24 @@ export class ServiceCollection implements IServiceCollection {
return this.add(serviceType, implementationOrFactory, 'singleton');
}
/**
* Registers a scoped service with the collection.
*
* Scoped services are created once per scope. This is useful for services that should be
* shared within a request but not across requests.
*
* @template T - The type of the service to register
* @param serviceType - The service identifier
* @param implementationOrFactory - The implementation class or factory function
* @returns The service collection instance for method chaining
*
* @example
* ```typescript
* services.addScoped(userServiceIdentifier, UserService);
* // or with a factory:
* services.addScoped(userServiceIdentifier, (provider) => new UserService(provider.getService(loggerIdentifier)));
* ```
*/
public addScoped<T>(
serviceType: ServiceIdentifier<T>,
implementationOrFactory: ServiceFactory<T> | Constructor<T>,
@@ -27,6 +73,23 @@ export class ServiceCollection implements IServiceCollection {
return this.add(serviceType, implementationOrFactory, 'scoped');
}
/**
* Registers a transient service with the collection.
*
* Transient services are created each time they are requested.
*
* @template T - The type of the service to register
* @param serviceType - The service identifier
* @param implementationOrFactory - The implementation class or factory function
* @returns The service collection instance for method chaining
*
* @example
* ```typescript
* services.addTransient(userServiceIdentifier, UserService);
* // or with a factory:
* services.addTransient(userServiceIdentifier, (provider) => new UserService(provider.getService(loggerIdentifier)));
* ```
*/
public addTransient<T>(
serviceType: ServiceIdentifier<T>,
implementationOrFactory: ServiceFactory<T> | Constructor<T>,
@@ -34,10 +97,24 @@ export class ServiceCollection implements IServiceCollection {
return this.add(serviceType, implementationOrFactory, 'transient');
}
/**
* Builds a service provider from the registered services.
*
* @returns A new service provider that can resolve the registered services
*/
public build(): IServiceProvider {
return new ServiceProvider(this._descriptors);
}
/**
* Internal method to add a service descriptor to the collection.
*
* @template T - The type of the service to register
* @param serviceType - The service identifier
* @param implementationOrFactory - The implementation class or factory function
* @param lifetime - The service lifetime
* @returns The service collection instance for method chaining
*/
private add<T>(
serviceType: ServiceIdentifier<T>,
implementationOrFactory: ServiceFactory<T> | Constructor<T>,
@@ -65,6 +142,12 @@ export class ServiceCollection implements IServiceCollection {
return this;
}
/**
* Checks if a function is a constructor
*
* @param func - The function to check
* @returns True if the function is a constructor, false otherwise
*/
private isConstructor(func: unknown): func is Constructor<unknown> {
return typeof func === 'function' &&
!!func.prototype &&
+88
View File
@@ -7,23 +7,66 @@ import type {
Constructor,
} from './types.js';
/**
* Represents a scope for scoped services.
*
* A service scope provides access to scoped services that are created once per scope.
*
* @implements {IServiceScope}
*/
export class ServiceScope implements IServiceScope {
/**
* The service provider associated with this scope
*/
public readonly serviceProvider: IServiceProvider;
/**
* Creates a new service scope
*
* @param serviceProvider - The service provider for this scope
*/
constructor(serviceProvider: IServiceProvider) {
this.serviceProvider = serviceProvider;
}
/**
* Disposes the scope and clears any scoped service instances
*/
dispose(): void {
this.serviceProvider.dispose();
}
}
/**
* A provider that can resolve registered services by their service identifier.
*
* The service provider is responsible for creating and managing service instances
* according to their registered lifetime.
*
* @implements {IServiceProvider}
*/
export class ServiceProvider implements IServiceProvider {
/**
* Map of service descriptors by service identifier
*/
private readonly _descriptors: Map<ServiceIdentifier<unknown>, ServiceDescriptor<unknown>>;
/**
* Map of singleton service instances by service identifier
*/
private readonly _singletonInstances: Map<ServiceIdentifier<unknown>, unknown> = new Map();
/**
* Map of scoped service instances by service identifier
*/
private readonly _scopedInstances: Map<ServiceIdentifier<unknown>, unknown> = new Map();
/**
* Creates a new service provider
*
* @param descriptors - Map of service descriptors
* @param parent - Optional parent service provider to inherit singleton instances from
*/
constructor(
descriptors: Map<ServiceIdentifier<unknown>, ServiceDescriptor<unknown>>,
parent?: ServiceProvider,
@@ -36,6 +79,7 @@ export class ServiceProvider implements IServiceProvider {
});
}
// Pre-resolve singleton services
for (const descriptor of descriptors.values()) {
if (descriptor.lifetime === 'singleton') {
this.resolveService(descriptor);
@@ -43,6 +87,19 @@ export class ServiceProvider implements IServiceProvider {
}
}
/**
* Gets a service instance by its service identifier
*
* @template T - The type of the service to resolve
* @param serviceType - The service identifier of the service to resolve
* @returns The resolved service instance
* @throws Error if the service is not registered
*
* @example
* ```typescript
* const userService = serviceProvider.getService(userServiceIdentifier);
* ```
*/
getService<T>(serviceType: ServiceIdentifier<T>): T {
const descriptor = this._descriptors.get(serviceType);
@@ -53,15 +110,39 @@ export class ServiceProvider implements IServiceProvider {
return this.resolveService(descriptor as ServiceDescriptor<T>);
}
/**
* Creates a new scope for scoped services
*
* @returns A new service scope
*
* @example
* ```typescript
* const scope = serviceProvider.createScope();
* const scopedService = scope.serviceProvider.getService(serviceIdentifier);
* // ... use scoped service
* scope.dispose();
* ```
*/
createScope(): IServiceScope {
const scopedProvider = new ServiceProvider(this._descriptors, this);
return new ServiceScope(scopedProvider);
}
/**
* Disposes the service provider and clears any scoped service instances
*/
dispose(): void {
this._scopedInstances.clear();
}
/**
* Resolves a service instance from its descriptor
*
* @template T - The type of the service to resolve
* @param descriptor - The service descriptor
* @returns The resolved service instance
* @throws Error if the service lifetime is unknown
*/
private resolveService<T>(descriptor: ServiceDescriptor<T>): T {
const { serviceType, implementationType, lifetime, factory } = descriptor;
@@ -89,6 +170,13 @@ export class ServiceProvider implements IServiceProvider {
}
}
/**
* Creates an instance of a service class and resolves its dependencies
*
* @template T - The type of the service to create
* @param ctor - The constructor of the service class
* @returns A new instance of the service class with its dependencies resolved
*/
private createInstance<T>(ctor: Constructor<T>): T {
const paramTypes = Reflect.getMetadata('design:paramtypes', ctor) ?? [];
+149
View File
@@ -1,40 +1,189 @@
/**
* Represents the possible lifetimes for registered services
*
* - singleton: Created once and shared by all consumers
* - scoped: Created once per scope
* - transient: Created each time they are requested
*/
export type ServiceLifetime = 'singleton' | 'scoped' | 'transient';
/**
* Represents a constructor function for a class
*
* @template T - The type of object the constructor creates
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Constructor<T> = new (...args: any[]) => T;
/**
* A unique identifier for a service type
*
* @template T - The type of the service
*/
export type ServiceIdentifier<T = unknown> = symbol & { __brand: T };
/**
* A factory function that creates a service instance
*
* @template T - The type of the service to create
*/
export type ServiceFactory<T> = (provider: IServiceProvider) => T;
/**
* Descriptor for a registered service
*
* @template T - The type of the service
*/
export type ServiceDescriptor<T = unknown> = {
/**
* The service identifier
*/
serviceType: ServiceIdentifier<T>;
/**
* The implementation class constructor
*/
implementationType: Constructor<T>;
/**
* The service lifetime
*/
lifetime: ServiceLifetime;
/**
* Optional factory function to create the service instance
*/
factory?: ServiceFactory<T>;
};
/**
* Creates a typed service identifier
*
* @template T - The type of the service
* @returns A unique identifier for the service type
*
* @example
* ```typescript
* interface IUserService {
* getUserById(id: string): Promise<User>;
* }
*
* const userServiceIdentifier = createServiceIdentifier<IUserService>();
* ```
*/
export function createServiceIdentifier<T>(): ServiceIdentifier<T> {
return Symbol() as ServiceIdentifier<T>;
}
/**
* Interface for a collection of service descriptors that can be used to build a service provider
*/
export interface IServiceCollection {
/**
* Registers a singleton service with the collection
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param implementationType - The implementation class
* @returns The service collection for method chaining
*/
addSingleton<T>(serviceType: ServiceIdentifier<T>, implementationType: Constructor<T>): IServiceCollection;
/**
* Registers a singleton service with a factory function
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param factory - A factory function that creates the service instance
* @returns The service collection for method chaining
*/
addSingleton<T>(serviceType: ServiceIdentifier<T>, factory: ServiceFactory<T>): IServiceCollection;
/**
* Registers a scoped service with the collection
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param implementationType - The implementation class
* @returns The service collection for method chaining
*/
addScoped<T>(serviceType: ServiceIdentifier<T>, implementationType: Constructor<T>): IServiceCollection;
/**
* Registers a scoped service with a factory function
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param factory - A factory function that creates the service instance
* @returns The service collection for method chaining
*/
addScoped<T>(serviceType: ServiceIdentifier<T>, factory: ServiceFactory<T>): IServiceCollection;
/**
* Registers a transient service with the collection
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param implementationType - The implementation class
* @returns The service collection for method chaining
*/
addTransient<T>(serviceType: ServiceIdentifier<T>, implementationType: Constructor<T>): IServiceCollection;
/**
* Registers a transient service with a factory function
*
* @template T - The type of the service
* @param serviceType - The service identifier
* @param factory - A factory function that creates the service instance
* @returns The service collection for method chaining
*/
addTransient<T>(serviceType: ServiceIdentifier<T>, factory: ServiceFactory<T>): IServiceCollection;
/**
* Builds a service provider from the registered services
*
* @returns A new service provider instance
*/
build(): IServiceProvider;
}
/**
* Interface for a provider that can resolve services by their service identifier
*/
export interface IServiceProvider {
/**
* Gets a service instance by its service identifier
*
* @template T - The type of the service to resolve
* @param serviceType - The service identifier
* @returns The resolved service instance
*/
getService<T>(serviceType: ServiceIdentifier<T>): T;
/**
* Creates a new scope for scoped services
*
* @returns A new service scope
*/
createScope(): IServiceScope;
/**
* Disposes the service provider and clears any scoped service instances
*/
dispose(): void;
}
/**
* Interface for a scope that provides access to scoped services
*/
export interface IServiceScope {
/**
* The service provider for this scope
*/
serviceProvider: IServiceProvider;
/**
* Disposes the scope and clears any scoped service instances
*/
dispose(): void;
}