From 24dfdcd2b1f8927e17b7dd5184be62a8e67cfcb1 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 13:54:23 +0200 Subject: [PATCH 01/10] feat: native Decorator Metadata (Symbol.metadata) We have now migrated the library to use native decorators instead of reflect-metadata BREAKING CHANGE: deprecates reflect-metadata in favor of native decorator metadata #20 --- libs/core/src/constants.ts | 11 +++ libs/core/src/container.test.ts | 108 ++++++++++++------------ libs/core/src/container.ts | 131 +++++++++++++++-------------- libs/core/src/decorators.test.ts | 136 ++++++++++--------------------- libs/core/src/decorators.ts | 45 ++++------ libs/core/src/guards.test.ts | 95 +++++++++++++++++++++ libs/core/src/guards.ts | 72 ++++++++++++++++ libs/core/src/helpers.test.ts | 33 ++++++++ libs/core/src/helpers.ts | 19 +++++ libs/core/src/index.ts | 13 ++- libs/core/src/module.test.ts | 58 +++++-------- libs/core/src/module.ts | 11 ++- libs/core/src/types.test.ts | 64 +++++++-------- libs/core/src/types.ts | 53 ++++++------ libs/core/tsconfig.lib.json | 3 +- 15 files changed, 504 insertions(+), 348 deletions(-) create mode 100644 libs/core/src/constants.ts create mode 100644 libs/core/src/guards.test.ts create mode 100644 libs/core/src/guards.ts create mode 100644 libs/core/src/helpers.test.ts create mode 100644 libs/core/src/helpers.ts diff --git a/libs/core/src/constants.ts b/libs/core/src/constants.ts new file mode 100644 index 0000000..97cafd6 --- /dev/null +++ b/libs/core/src/constants.ts @@ -0,0 +1,11 @@ +// Tokenized constants for NexusDI core + +export const SYMBOL_METADATA = 'Symbol.metadata'; + +export const METADATA_KEYS = { + DESIGN_PARAMTYPES: 'design:paramtypes', + DESIGN_TYPE: 'design:type', + INJECT_METADATA: 'nexusdi:inject', + SERVICE_METADATA: 'nexusdi:service', + MODULE_METADATA: 'nexusdi:module', +} as const; diff --git a/libs/core/src/container.test.ts b/libs/core/src/container.test.ts index 9c986d7..e24c450 100644 --- a/libs/core/src/container.test.ts +++ b/libs/core/src/container.test.ts @@ -1,7 +1,8 @@ -import 'reflect-metadata'; import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { Nexus, Module, Service, Provider, Inject, Token } from './index'; -import type { TokenType } from './types'; +import { Nexus } from './container'; +import { Inject, Service, Module, Provider } from './decorators'; +import { Token } from './token'; +import { InvalidToken } from './container'; describe('Nexus', () => { let nexus: Nexus; @@ -56,9 +57,10 @@ describe('Nexus', () => { * Value: Ensures missing providers are caught early and feedback is clear */ it('should throw error for unregistered token', () => { - expect(() => - nexus.get('UNREGISTERED_TOKEN' as unknown as TokenType) - ).toThrow('No provider found for token: UNREGISTERED_TOKEN'); + class Unregistered {} + expect(() => nexus.get(Unregistered)).toThrow( + 'No provider found for token: Unregistered' + ); }); /** * Test: Token registration check @@ -97,8 +99,7 @@ describe('Nexus', () => { @Service() class UserServiceWithLogger { constructor( - // @ts-expect-error: Decorator signature is not type-safe for parameter injection, intentional for test - @Inject(LoggerService as unknown as TokenType) + @Inject(LoggerService) private logger: LoggerService ) {} getUser(id: string): string { @@ -124,27 +125,22 @@ describe('Nexus', () => { * Value: Enables advanced DI scenarios with custom keys */ it('should work with custom tokens', () => { - const API_URL = 'API_URL'; + const API_URL = new Token('API_URL'); const CONFIG_TOKEN = new Token('CONFIG'); - @Provider(CONFIG_TOKEN as unknown as TokenType) + @Provider(CONFIG_TOKEN) class ConfigService { - constructor( - // @ts-expect-error: Decorator signature is not type-safe for parameter injection, intentional for test - @Inject(API_URL as unknown as TokenType) private apiUrl: string - ) {} + constructor(@Inject(API_URL) private apiUrl: string) {} getApiUrl(): string { return this.apiUrl; } } - nexus.set(API_URL as unknown as TokenType, { + nexus.set(API_URL, { useValue: 'https://api.example.com', }); - nexus.set(CONFIG_TOKEN as unknown as TokenType, { + nexus.set(CONFIG_TOKEN, { useClass: ConfigService, }); - const config = nexus.get( - CONFIG_TOKEN as unknown as TokenType - ) as ConfigService; + const config = nexus.get(CONFIG_TOKEN) as ConfigService; expect(config.getApiUrl()).toBe('https://api.example.com'); }); /** @@ -153,11 +149,12 @@ describe('Nexus', () => { * Value: Enables dynamic provider creation in DI */ it('should work with factory providers', () => { + const FACTORY_TOKEN = new Token('FACTORY_TOKEN'); const factory = vi.fn().mockReturnValue('factory-result'); - nexus.set('FACTORY_TOKEN' as unknown as TokenType, { + nexus.set(FACTORY_TOKEN, { useFactory: factory, }); - const result = nexus.get('FACTORY_TOKEN' as unknown as TokenType); + const result = nexus.get(FACTORY_TOKEN); expect(result).toBe('factory-result'); expect(factory).toHaveBeenCalledTimes(1); }); @@ -167,13 +164,13 @@ describe('Nexus', () => { * Value: Ensures DI can resolve and inject dependencies for factories */ it('should work with factory providers with dependencies', () => { - const DEP1 = 'DEP1'; - const DEP2 = 'DEP2'; - const FACTORY_TOKEN = 'FACTORY_WITH_DEPS'; + const DEP1 = new Token('DEP1'); + const DEP2 = new Token('DEP2'); + const FACTORY_TOKEN = new Token('FACTORY_WITH_DEPS'); // Register dependencies - nexus.set(DEP1 as unknown as TokenType, { useValue: 'dependency1' }); - nexus.set(DEP2 as unknown as TokenType, { useValue: 'dependency2' }); + nexus.set(DEP1, { useValue: 'dependency1' }); + nexus.set(DEP2, { useValue: 'dependency2' }); // Create factory that expects dependencies const factory = vi @@ -183,15 +180,12 @@ describe('Nexus', () => { }); // Register factory with dependencies - nexus.set(FACTORY_TOKEN as unknown as TokenType, { + nexus.set(FACTORY_TOKEN, { useFactory: factory, - deps: [ - DEP1 as unknown as TokenType, - DEP2 as unknown as TokenType, - ], + deps: [DEP1, DEP2], }); - const result = nexus.get(FACTORY_TOKEN as unknown as TokenType); + const result = nexus.get(FACTORY_TOKEN); expect(result).toBe('dependency1-dependency2-result'); expect(factory).toHaveBeenCalledWith('dependency1', 'dependency2'); }); @@ -202,12 +196,21 @@ describe('Nexus', () => { */ it('should work with auto-generated tokens', () => { const autoToken = new Token(); - nexus.set(autoToken as unknown as TokenType, { + nexus.set(autoToken, { useValue: 'auto-generated-value', }); - const result = nexus.get(autoToken as unknown as TokenType); + const result = nexus.get(autoToken); expect(result).toBe('auto-generated-value'); }); + + it('should throw InvalidToken for invalid token types', () => { + // @ts-expect-error + expect(() => nexus.get('INVALID')).toThrowError(InvalidToken); + // @ts-expect-error + expect(() => nexus.set('INVALID', { useValue: 123 })).toThrowError( + InvalidToken + ); + }); }); // Module registration group: Ensures modules can register services @@ -227,8 +230,7 @@ describe('Nexus', () => { @Service() class UserServiceWithLogger { constructor( - // @ts-expect-error: Decorator signature is not type-safe for parameter injection, intentional for test - @Inject(LoggerService as unknown as TokenType) + @Inject(LoggerService) private logger: LoggerService ) {} getUser(id: string): string { @@ -340,13 +342,8 @@ describe('Nexus', () => { } } const container = new Nexus(); - container.set( - USER_SERVICE as unknown as TokenType, - UserService - ); - const userService = container.get( - USER_SERVICE as unknown as TokenType - ) as UserService; + container.set(USER_SERVICE, UserService); + const userService = container.get(USER_SERVICE) as UserService; expect(userService.getUsers()).toEqual(['Alice', 'Bob', 'Charlie']); }); }); @@ -359,8 +356,9 @@ describe('Nexus', () => { * Value: Ensures misconfigured providers are caught early */ it('should throw if provider is missing useClass, useValue, and useFactory', () => { - nexus.set('INVALID' as unknown as TokenType, {} as any); - expect(() => nexus.get('INVALID' as unknown as TokenType)).toThrow( + class Invalid {} + nexus.set(Invalid, {} as any); + expect(() => nexus.get(Invalid)).toThrow( /Invalid provider configuration/ ); }); @@ -377,12 +375,10 @@ describe('Nexus', () => { return 42; } } - const ALIAS = 'ALIAS'; - nexus.set(ALIAS as unknown as TokenType, { useClass: AliasService }); - const instance1 = nexus.get( - ALIAS as unknown as TokenType - ) as AliasService; - const instance2 = nexus.get(AliasService) as AliasService; + const ALIAS = Symbol('ALIAS'); + nexus.set(ALIAS, { useClass: AliasService }); + const instance1 = nexus.get(ALIAS) as AliasService; + const instance2 = nexus.get(AliasService); expect(instance1).toBe(instance2); expect(instance1.getValue()).toBe(42); }); @@ -393,11 +389,11 @@ describe('Nexus', () => { * Value: Ensures predictable provider overriding */ it('should allow overwriting providers for the same token', () => { - const TOKEN = 'OVERRIDE'; - nexus.set(TOKEN as unknown as TokenType, { useValue: 1 }); - expect(nexus.get(TOKEN as unknown as TokenType)).toBe(1); - nexus.set(TOKEN as unknown as TokenType, { useValue: 2 }); - expect(nexus.get(TOKEN as unknown as TokenType)).toBe(2); + const TOKEN = new Token('OVERRIDE'); + nexus.set(TOKEN, { useValue: 1 }); + expect(nexus.get(TOKEN)).toBe(1); + nexus.set(TOKEN, { useValue: 2 }); + expect(nexus.get(TOKEN)).toBe(2); }); /** diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index 51cdf4e..db9a362 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -6,8 +6,16 @@ import type { InjectionMetadata, ModuleProvider, } from './types'; -import { METADATA_KEYS } from './types'; import { Token } from './token'; +import { METADATA_KEYS } from './constants'; +import { getMetadata } from './helpers'; +import { + isTokenType, + isProvider, + isFactory, + isService, + isContainer, +} from './guards'; /** * The main DI container class for NexusDI. Use this to bootstrap and resolve your modules and services. @@ -35,9 +43,12 @@ export class Nexus implements IContainer { * @see https://nexus.js.org/docs/container/nexus-class */ get(token: Token): T; + get(token: symbol): T; get(token: new (...args: any[]) => T): T; - get(token: string): T; get(token: any): T { + if (!isTokenType(token)) { + throw new InvalidToken(token); + } // Resolve aliases const actualToken = this.aliases.get(token) || token; if (!this.has(actualToken)) { @@ -64,7 +75,7 @@ export class Nexus implements IContainer { if (provider.useValue !== undefined) { instance = provider.useValue; } else if (provider.useFactory) { - const deps = provider.deps?.map((dep) => this.get(dep)) || []; + const deps = provider.deps?.map((dep) => this.get(dep as any)) || []; instance = provider.useFactory(...deps); } else if (provider.useClass) { instance = this.resolve(provider.useClass); @@ -86,9 +97,9 @@ export class Nexus implements IContainer { * @returns True if registered * @see https://nexus.js.org/docs/container/nexus-class */ - has(token: Token): boolean; - has(token: new (...args: any[]) => unknown): boolean; - has(token: string): boolean; + has(token: Token): boolean; + has(token: symbol): boolean; + has(token: new (...args: any[]) => any): boolean; has(token: any): boolean { const actualToken = this.aliases.get(token) || token; return ( @@ -103,15 +114,19 @@ export class Nexus implements IContainer { * @returns The resolved instance * @see https://nexus.js.org/docs/container/nexus-class */ - resolve(token: TokenType): T { + resolve(token: Token): T; + resolve(token: symbol): T; + resolve(token: new (...args: any[]) => T): T; + resolve(token: any): T { + if (!isTokenType(token)) { + throw new InvalidToken(token); + } const paramTypes = - Reflect.getMetadata(METADATA_KEYS.DESIGN_PARAMTYPES, token) || []; - // Get constructor parameter injection metadata from the constructor + getMetadata(token, METADATA_KEYS.DESIGN_PARAMTYPES) || []; const ctorInjectionMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, token) || []; - // Get property injection metadata from the prototype + getMetadata(token, METADATA_KEYS.INJECT_METADATA) || []; const propInjectionMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, token.prototype) || []; + getMetadata(token.prototype, METADATA_KEYS.INJECT_METADATA) || []; // Create parameter array for constructor const params: any[] = new Array(paramTypes.length); @@ -163,13 +178,13 @@ export class Nexus implements IContainer { */ set(token: Token, provider: Provider): void; set(token: Token, serviceClass: new (...args: any[]) => T): void; + set(token: symbol, provider: Provider): void; + set(token: symbol, serviceClass: new (...args: any[]) => T): void; set(token: new (...args: any[]) => T, provider: Provider): void; set( token: new (...args: any[]) => T, serviceClass: new (...args: any[]) => T ): void; - set(token: string, provider: Provider): void; - set(token: string, serviceClass: new (...args: any[]) => T): void; set(moduleClass: new (...args: any[]) => any): void; set(moduleConfig: { providers?: ModuleProvider[]; @@ -181,9 +196,7 @@ export class Nexus implements IContainer { // If it's a module class (has @Module metadata) if ( typeof tokenOrModuleOrConfig === 'function' && - Reflect && - Reflect.getMetadata && - Reflect.getMetadata(METADATA_KEYS.MODULE_METADATA, tokenOrModuleOrConfig) + getMetadata(tokenOrModuleOrConfig, METADATA_KEYS.MODULE_METADATA) ) { if (this.inProgressModules.has(tokenOrModuleOrConfig)) { return; @@ -193,9 +206,9 @@ export class Nexus implements IContainer { } this.inProgressModules.add(tokenOrModuleOrConfig); this.modules.add(tokenOrModuleOrConfig); - const moduleConfig = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - tokenOrModuleOrConfig + const moduleConfig = getMetadata( + tokenOrModuleOrConfig, + METADATA_KEYS.MODULE_METADATA ); if (!moduleConfig) { this.inProgressModules.delete(tokenOrModuleOrConfig); @@ -229,6 +242,9 @@ export class Nexus implements IContainer { token: TokenType, providerOrClass: Provider | (new (...args: any[]) => any) ): void { + if (!isTokenType(token)) { + throw new InvalidToken(token); + } let provider: Provider; if (typeof providerOrClass === 'function') { provider = { useClass: providerOrClass }; @@ -249,43 +265,6 @@ export class Nexus implements IContainer { } } - /** - * @deprecated Use the unified set() method instead. - */ - setModule(moduleClass: new (...args: any[]) => any): void { - console.warn( - '[DEPRECATED] Use container.set(moduleClass) instead of setModule.' - ); - if (this.modules.has(moduleClass)) { - return; // Module already registered - } - this.modules.add(moduleClass); - const moduleConfig = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - moduleClass - ); - if (!moduleConfig) { - throw new Error( - `Module ${moduleClass.name} is not properly decorated with @Module` - ); - } - this.processModuleConfig(moduleConfig); - } - - /** - * @deprecated Use the unified set() method instead. - */ - registerDynamicModule(moduleConfig: { - services?: (new (...args: any[]) => any)[]; - providers?: ModuleProvider[]; - imports?: (new (...args: any[]) => any)[]; - }): void { - console.warn( - '[DEPRECATED] Use container.set(dynamicModuleConfig) instead of registerDynamicModule.' - ); - this.processModuleConfig(moduleConfig); - } - /** * Process module configuration (shared between setModule and registerDynamicModule) */ @@ -297,16 +276,16 @@ export class Nexus implements IContainer { // Register imported modules if (moduleConfig.imports) { for (const importedModule of moduleConfig.imports) { - this.setModule(importedModule); + this.set(importedModule); } } // Register services if (moduleConfig.services) { for (const serviceClass of moduleConfig.services) { - const serviceConfig = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - serviceClass + const serviceConfig = getMetadata( + serviceClass, + METADATA_KEYS.SERVICE_METADATA ); if (!serviceConfig) { throw new Error( @@ -323,10 +302,10 @@ export class Nexus implements IContainer { if (moduleConfig.providers) { for (const provider of moduleConfig.providers) { // Check if provider is a service class (has @Service decorator) - if (typeof provider === 'function') { - const serviceConfig = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - provider + if (isService(provider)) { + const serviceConfig = getMetadata( + provider, + METADATA_KEYS.SERVICE_METADATA ); if (serviceConfig) { this.set(serviceConfig.token as TokenType, { @@ -337,9 +316,11 @@ export class Nexus implements IContainer { `Service class ${provider.name} is not decorated with @Service` ); } - } else { + } else if (isProvider(provider)) { // Full provider object this.set(provider.token as TokenType, provider); + } else { + throw new Error('Invalid provider type'); } } } @@ -405,3 +386,21 @@ export class Nexus implements IContainer { }; } } + +export class ContainerException extends Error { + constructor(message: string) { + super(message); + this.name = 'ContainerException'; + } +} + +export class InvalidToken extends ContainerException { + constructor(token: any) { + super( + `Invalid token: ${String( + token + )}. Only class constructors, symbols, or Token instances are allowed as tokens.` + ); + this.name = 'InvalidToken'; + } +} diff --git a/libs/core/src/decorators.test.ts b/libs/core/src/decorators.test.ts index 52fdf8f..061006c 100644 --- a/libs/core/src/decorators.test.ts +++ b/libs/core/src/decorators.test.ts @@ -1,22 +1,28 @@ -import 'reflect-metadata'; import { describe, it, expect, beforeEach } from 'vitest'; import { Module, Service, Provider, Inject } from './decorators'; import { Token } from './token'; -import { METADATA_KEYS } from './types'; +import { getMetadata } from './helpers'; +import { METADATA_KEYS } from './constants'; describe('Decorators', () => { beforeEach(() => { // Clear metadata before each test - only clear what exists - Reflect.deleteMetadata(METADATA_KEYS.MODULE_METADATA, class TestModule {}); - Reflect.deleteMetadata( - METADATA_KEYS.SERVICE_METADATA, - class TestService {} - ); - Reflect.deleteMetadata(METADATA_KEYS.INJECT_METADATA, class TestService {}); - Reflect.deleteMetadata( - METADATA_KEYS.INJECT_METADATA, - class TestService {}.prototype - ); + if ((class TestModule {} as any)[(Symbol as any).metadata]) + delete (class TestModule {} as any)[(Symbol as any).metadata][ + METADATA_KEYS.MODULE_METADATA + ]; + if ((class TestService {} as any)[(Symbol as any).metadata]) + delete (class TestService {} as any)[(Symbol as any).metadata][ + METADATA_KEYS.SERVICE_METADATA + ]; + if ((class TestService {} as any)[(Symbol as any).metadata]) + delete (class TestService {} as any)[(Symbol as any).metadata][ + METADATA_KEYS.INJECT_METADATA + ]; + if ((class TestService {}.prototype as any)[(Symbol as any).metadata]) + delete (class TestService {}.prototype as any)[(Symbol as any).metadata][ + METADATA_KEYS.INJECT_METADATA + ]; }); // @Module decorator group: Ensures module metadata is attached and correct @@ -35,10 +41,7 @@ describe('Decorators', () => { }) class TestModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TestModule - ); + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toEqual({ imports: [], services: [], @@ -60,10 +63,7 @@ describe('Decorators', () => { }) class TestModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TestModule - ); + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); expect(metadata.services).toEqual([TestService]); }); @@ -73,19 +73,16 @@ describe('Decorators', () => { * Value: Ensures provider registration in modules works as expected */ it('should handle module with providers', () => { - const testToken = new Token('TEST'); + const TEST_TOKEN = new Token('TEST'); @Module({ - providers: [{ token: testToken, useClass: class TestProvider {} }], + providers: [{ token: TEST_TOKEN, useClass: class TestProvider {} }], }) class TestModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TestModule - ); + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); expect(metadata.providers).toHaveLength(1); - expect(metadata.providers[0].token).toBe(testToken); + expect(metadata.providers[0].token).toBe(TEST_TOKEN); }); /** @@ -101,10 +98,7 @@ describe('Decorators', () => { }) class TestModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TestModule - ); + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); expect(metadata.imports).toEqual([ImportedModule]); }); }); @@ -120,10 +114,7 @@ describe('Decorators', () => { @Service() class TestService {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - TestService - ); + const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); expect(metadata.token).toBe(TestService); }); @@ -138,28 +129,9 @@ describe('Decorators', () => { @Service(customToken) class TestService {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - TestService - ); + const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); expect(metadata.token).toBe(customToken); }); - - /** - * Test: Adds service metadata with string token - * Validates: Token is the string - * Value: Allows for string-based DI tokens for convenience - */ - it('should add service metadata with string token', () => { - @Service('STRING_TOKEN') - class TestService {} - - const metadata = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - TestService - ); - expect(metadata.token).toBe('STRING_TOKEN'); - }); }); // @Provider decorator group: Ensures provider classes are marked for DI @@ -179,18 +151,6 @@ describe('Decorators', () => { // The actual provider registration happens in the container expect(TestProvider).toBeDefined(); }); - - /** - * Test: Works with string token - * Validates: Class is defined and usable - * Value: Allows for string-based provider tokens - */ - it('should work with string token', () => { - @Provider('STRING_PROVIDER') - class TestProvider {} - - expect(TestProvider).toBeDefined(); - }); }); // @Inject decorator group: Ensures injection metadata is attached and correct @@ -207,10 +167,7 @@ describe('Decorators', () => { constructor(@Inject(injectToken) dependency: any) {} } - const metadata = Reflect.getMetadata( - METADATA_KEYS.INJECT_METADATA, - TestService - ); + const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); expect(metadata).toHaveLength(1); expect(metadata[0].token).toBe(injectToken); expect(metadata[0].index).toBe(0); @@ -230,10 +187,7 @@ describe('Decorators', () => { constructor(@Inject(token1) dep1: any, @Inject(token2) dep2: any) {} } - const metadata = Reflect.getMetadata( - METADATA_KEYS.INJECT_METADATA, - TestService - ); + const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); expect(metadata).toHaveLength(2); // Check that both tokens are present, but don't assume order @@ -264,13 +218,13 @@ describe('Decorators', () => { constructor(@Inject(dependencyToken) dep: any) {} } - const serviceMetadata = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - TestService + const serviceMetadata = getMetadata( + TestService, + METADATA_KEYS.SERVICE_METADATA ); - const injectMetadata = Reflect.getMetadata( - METADATA_KEYS.INJECT_METADATA, - TestService + const injectMetadata = getMetadata( + TestService, + METADATA_KEYS.INJECT_METADATA ); expect(serviceMetadata.token).toBe(serviceToken); @@ -284,29 +238,29 @@ describe('Decorators', () => { * Value: Ensures modules and services can be composed for application DI */ it('should work with complete module setup', () => { - const serviceToken = new Token('MODULE_SERVICE'); + const SERVICE_TOKEN = new Token('MODULE_SERVICE'); - @Service(serviceToken) + @Service(SERVICE_TOKEN) class ModuleService {} @Module({ services: [ModuleService], - providers: [{ token: 'CONFIG', useValue: 'config' }], + providers: [{ token: new Token('CONFIG'), useValue: 'config' }], }) class TestModule {} - const moduleMetadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TestModule + const moduleMetadata = getMetadata( + TestModule, + METADATA_KEYS.MODULE_METADATA ); - const serviceMetadata = Reflect.getMetadata( - METADATA_KEYS.SERVICE_METADATA, - ModuleService + const serviceMetadata = getMetadata( + ModuleService, + METADATA_KEYS.SERVICE_METADATA ); expect(moduleMetadata.services).toEqual([ModuleService]); expect(moduleMetadata.providers).toHaveLength(1); - expect(serviceMetadata.token).toBe(serviceToken); + expect(serviceMetadata.token).toBe(SERVICE_TOKEN); }); }); }); diff --git a/libs/core/src/decorators.ts b/libs/core/src/decorators.ts index 5ae75a7..5a23545 100644 --- a/libs/core/src/decorators.ts +++ b/libs/core/src/decorators.ts @@ -2,10 +2,11 @@ // TypeScript's type system cannot express decorator overloads with union implementation signatures for DI ergonomics. // This file disables type checking to allow ergonomic and type-safe decorator APIs for users. // See: https://github.com/microsoft/TypeScript/issues/37181 for context on why this is necessary. -import 'reflect-metadata'; import type { TokenType, ServiceConfig, ModuleConfig } from './types'; -import { METADATA_KEYS, type InjectionMetadata } from './types'; +import { METADATA_KEYS } from './constants'; import type { Token } from './token'; +import { SYMBOL_METADATA } from './constants'; +import { setMetadata, getMetadata } from './helpers'; /** * Decorator that marks a class as a DI module, allowing you to group providers, services, and imports. @@ -31,7 +32,7 @@ import type { Token } from './token'; */ export function Module(config: ModuleConfig) { return (target: new (...args: any[]) => any) => { - Reflect.defineMetadata(METADATA_KEYS.MODULE_METADATA, config, target); + setMetadata(target, METADATA_KEYS.MODULE_METADATA, config); }; } @@ -65,7 +66,7 @@ export function Service( const config: ServiceConfig = { token: token || (target as TokenType), }; - Reflect.defineMetadata(METADATA_KEYS.SERVICE_METADATA, config, target); + setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); }; } @@ -102,7 +103,7 @@ export function Provider( const config: ServiceConfig = { token, }; - Reflect.defineMetadata(METADATA_KEYS.SERVICE_METADATA, config, target); + setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); }; } @@ -155,35 +156,33 @@ export function Inject( // Parameter decorator const metadataTarget = target; const existingMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, metadataTarget) || - []; + getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; const metadata: InjectionMetadata = { token, index: parameterIndex, propertyKey: undefined, }; existingMetadata.push(metadata); - Reflect.defineMetadata( + setMetadata( + metadataTarget, METADATA_KEYS.INJECT_METADATA, - existingMetadata, - metadataTarget + existingMetadata ); } else if (propertyKey !== undefined) { // Property decorator const metadataTarget = target; const existingMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, metadataTarget) || - []; + getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; const metadata: InjectionMetadata = { token, index: 0, propertyKey, }; existingMetadata.push(metadata); - Reflect.defineMetadata( + setMetadata( + metadataTarget, METADATA_KEYS.INJECT_METADATA, - existingMetadata, - metadataTarget + existingMetadata ); } }; @@ -251,7 +250,7 @@ export function Optional( if (typeof parameterIndex === 'number') { // Parameter decorator const existingMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, target) || []; + getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; const metadata: InjectionMetadata = { token, index: parameterIndex, @@ -259,15 +258,11 @@ export function Optional( optional: true, }; existingMetadata.push(metadata); - Reflect.defineMetadata( - METADATA_KEYS.INJECT_METADATA, - existingMetadata, - target - ); + setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); } else if (propertyKey !== undefined) { // Property decorator const existingMetadata: InjectionMetadata[] = - Reflect.getMetadata(METADATA_KEYS.INJECT_METADATA, target) || []; + getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; const metadata: InjectionMetadata = { token, index: 0, @@ -275,11 +270,7 @@ export function Optional( optional: true, }; existingMetadata.push(metadata); - Reflect.defineMetadata( - METADATA_KEYS.INJECT_METADATA, - existingMetadata, - target - ); + setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); } }; } diff --git a/libs/core/src/guards.test.ts b/libs/core/src/guards.test.ts new file mode 100644 index 0000000..9debb37 --- /dev/null +++ b/libs/core/src/guards.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from 'vitest'; +import { + isTokenType, + isProvider, + isFactory, + isService, + isContainer, +} from './guards'; +import { Token } from './token'; +import type { Provider, IContainer } from './types'; + +/** + * Guards: Ensures all public guard functions work as expected for valid and invalid cases + */ +describe('Guards', () => { + describe('isTokenType', () => { + it('should return true for class constructors', () => { + class Test {} + expect(isTokenType(Test)).toBe(true); + }); + it('should return true for symbols', () => { + const sym = Symbol('SYM'); + expect(isTokenType(sym)).toBe(true); + }); + it('should return true for Token instances', () => { + const token = new Token('TEST'); + expect(isTokenType(token)).toBe(true); + }); + it('should return false for strings, numbers, objects, null, undefined', () => { + expect(isTokenType('string')).toBe(false); + expect(isTokenType(123)).toBe(false); + expect(isTokenType({})).toBe(false); + expect(isTokenType(null)).toBe(false); + expect(isTokenType(undefined)).toBe(false); + }); + }); + + describe('isProvider', () => { + it('should return true for objects with useClass, useValue, or useFactory', () => { + class Test {} + expect(isProvider({ useClass: Test })).toBe(true); + expect(isProvider({ useValue: 123 })).toBe(true); + expect(isProvider({ useFactory: () => 1 })).toBe(true); + }); + it('should return false for objects without provider keys', () => { + expect(isProvider({})).toBe(false); + expect(isProvider({ foo: 'bar' })).toBe(false); + expect(isProvider(null)).toBe(false); + expect(isProvider(undefined)).toBe(false); + }); + }); + + describe('isFactory', () => { + it('should return true for objects with useFactory function', () => { + expect(isFactory({ useFactory: () => 1 })).toBe(true); + }); + it('should return false for objects without useFactory or with non-function', () => { + expect(isFactory({ useFactory: 123 })).toBe(false); + expect(isFactory({ useClass: class {} })).toBe(false); + expect(isFactory({})).toBe(false); + expect(isFactory(null)).toBe(false); + expect(isFactory(undefined)).toBe(false); + }); + }); + + describe('isService', () => { + it('should return true for class constructors', () => { + class Test {} + expect(isService(Test)).toBe(true); + }); + it('should return false for objects, null, undefined', () => { + expect(isService({})).toBe(false); + expect(isService(null)).toBe(false); + expect(isService(undefined)).toBe(false); + }); + }); + + describe('isContainer', () => { + it('should return true for objects implementing IContainer', () => { + const fake: IContainer = { + get: () => 1, + set: () => {}, + has: () => true, + resolve: () => 1, + }; + expect(isContainer(fake)).toBe(true); + }); + it('should return false for objects missing required methods', () => { + expect(isContainer({ get: () => 1, set: () => {} })).toBe(false); + expect(isContainer({})).toBe(false); + expect(isContainer(null)).toBe(false); + expect(isContainer(undefined)).toBe(false); + }); + }); +}); diff --git a/libs/core/src/guards.ts b/libs/core/src/guards.ts new file mode 100644 index 0000000..c374c9e --- /dev/null +++ b/libs/core/src/guards.ts @@ -0,0 +1,72 @@ +// Type guards and validators for NexusDI public API +import type { TokenType, Provider, IContainer } from './types'; +import { Token } from './token'; + +/** + * Checks if a value is a Token instance. + */ +export function isToken(token: any): token is Token { + return !!( + token && + typeof token === 'object' && + token.constructor && + token.constructor.name === 'Token' + ); +} + +/** + * Checks if a value is a valid TokenType (class constructor, symbol, or Token instance). + */ +export function isTokenType(token: any): token is TokenType { + return ( + typeof token === 'function' || typeof token === 'symbol' || isToken(token) + ); +} + +/** + * Checks if a value is a Provider object (has useClass, useValue, or useFactory). + */ +export function isProvider(obj: any): obj is Provider { + return !!( + obj && + typeof obj === 'object' && + ('useClass' in obj || 'useValue' in obj || 'useFactory' in obj) + ); +} + +/** + * Checks if a value is a factory provider (has useFactory). + */ +export function isFactory(obj: any): obj is { useFactory: () => unknown } { + return !!( + obj && + typeof obj === 'object' && + typeof obj.useFactory === 'function' + ); +} + +/** + * Checks if a value is a service class (has a constructor, a non-empty name, and a prototype object). + */ +export function isService(obj: any): obj is new (...args: any[]) => any { + return ( + typeof obj === 'function' && + !!obj.name && + obj.prototype && + typeof obj.prototype === 'object' + ); +} + +/** + * Checks if a value is a NexusDI container (implements IContainer interface). + */ +export function isContainer(obj: any): obj is IContainer { + return !!( + obj && + typeof obj === 'object' && + typeof obj.get === 'function' && + typeof obj.set === 'function' && + typeof obj.has === 'function' && + typeof obj.resolve === 'function' + ); +} diff --git a/libs/core/src/helpers.test.ts b/libs/core/src/helpers.test.ts new file mode 100644 index 0000000..20e5cf0 --- /dev/null +++ b/libs/core/src/helpers.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import { setMetadata, getMetadata } from './helpers'; + +/** + * Helpers: Ensures setMetadata and getMetadata work as expected for Symbol.metadata + */ +describe('Helpers', () => { + it('should set and get metadata on a class', () => { + class Test {} + setMetadata(Test, 'foo', 123); + expect(getMetadata(Test, 'foo')).toBe(123); + }); + + it('should return undefined for missing metadata keys', () => { + class Test {} + expect(getMetadata(Test, 'missing')).toBeUndefined(); + }); + + it('should inherit metadata from parent class if not overridden', () => { + class Parent {} + setMetadata(Parent, 'bar', 'parent'); + class Child extends Parent {} + expect(getMetadata(Child, 'bar')).toBe('parent'); + }); + + it('should allow setting metadata on subclass independently', () => { + class Parent {} + class Child extends Parent {} + setMetadata(Child, 'baz', 'child'); + expect(getMetadata(Child, 'baz')).toBe('child'); + expect(getMetadata(Parent, 'baz')).toBeUndefined(); + }); +}); diff --git a/libs/core/src/helpers.ts b/libs/core/src/helpers.ts new file mode 100644 index 0000000..d19543c --- /dev/null +++ b/libs/core/src/helpers.ts @@ -0,0 +1,19 @@ +// Helpers for Symbol.metadata access +import { SYMBOL_METADATA } from './constants'; + +export function setMetadata(target: any, key: string, value: any) { + // Ensure the metadata object is own, not inherited + if (!Object.prototype.hasOwnProperty.call(target, (Symbol as any).metadata)) { + Object.defineProperty(target, (Symbol as any).metadata, { + value: {}, + enumerable: false, + configurable: true, + writable: true, + }); + } + (target as any)[(Symbol as any).metadata][key] = value; +} + +export function getMetadata(target: any, key: string) { + return (target as any)[(Symbol as any).metadata]?.[key]; +} diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index b2d43df..4ac4ee5 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -1,4 +1,11 @@ -import 'reflect-metadata'; +// NOTE: This file assumes tsconfig.json includes "lib": ["es2022", "esnext.decorators", ...] +// Symbol.metadata constant +import { SYMBOL_METADATA, METADATA_KEYS } from './constants'; +// Polyfill for Symbol.metadata +if (typeof (Symbol as any).metadata === 'undefined') { + (Symbol as any).metadata = Symbol(SYMBOL_METADATA); +} + // Core exports export { Nexus } from './container'; export type { IContainer } from './types'; @@ -30,8 +37,10 @@ export type { } from './types'; // Constants -export { METADATA_KEYS } from './types'; +export { SYMBOL_METADATA, METADATA_KEYS }; // Default export for convenience import { Nexus } from './container'; export default Nexus; + +export * from './guards'; diff --git a/libs/core/src/module.test.ts b/libs/core/src/module.test.ts index 6c0cd50..97e7f37 100644 --- a/libs/core/src/module.test.ts +++ b/libs/core/src/module.test.ts @@ -1,7 +1,8 @@ -import 'reflect-metadata'; import { Module } from './decorators'; import { DynamicModule } from './module'; -import { METADATA_KEYS, TokenType } from './types'; +import { getMetadata } from './helpers'; +import { METADATA_KEYS } from './constants'; +import type { TokenType } from './types'; // Dummy config token for testing const TEST_CONFIG_TOKEN = Symbol('TEST_CONFIG'); @@ -110,10 +111,7 @@ describe('Module decorator', () => { * for standard modules, not just DynamicModule. */ it('should attach metadata to a decorated class', () => { - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - BasicModule - ); + const metadata = getMetadata(BasicModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); expect(metadata.providers).toBeInstanceOf(Array); expect(metadata.services).toBeInstanceOf(Array); @@ -129,25 +127,24 @@ describe('Module inheritance', () => { * Value: Ensures that only explicitly decorated classes are treated as modules, preventing accidental inheritance of module status. */ it('should require @Module on subclass to be treated as a module', () => { - // In this DI system, every user-defined module should be decorated with @Module. - // If a subclass is not decorated, it should not be treated as a module. - // However, due to how Reflect.getMetadata works, metadata may be inherited. - // We recommend always decorating subclasses if they are to be modules. - // This test documents that expectation. - // (No assertion needed; this is a documentation/expectation test.) - // If you want to check for direct decoration only, use Reflect.getOwnMetadata. - const metadata = Reflect.getOwnMetadata( - METADATA_KEYS.MODULE_METADATA, - InheritedModule - ); - expect(metadata).toBeUndefined(); + class ProviderB {} + @Module({ providers: [ProviderB] }) + class ParentModule {} + class SubModule extends ParentModule {} + + // Should not have its own Symbol.metadata property + expect( + Object.prototype.hasOwnProperty.call(SubModule, (Symbol as any).metadata) + ).toBe(false); + + // Should inherit parent's MODULE_METADATA value + const parentMeta = getMetadata(ParentModule, METADATA_KEYS.MODULE_METADATA); + const subMeta = getMetadata(SubModule, METADATA_KEYS.MODULE_METADATA); + expect(subMeta).toEqual(parentMeta); }); it('should have metadata on parent if decorated', () => { - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - ParentModule - ); + const metadata = getMetadata(ParentModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); expect(metadata.providers).toBeInstanceOf(Array); }); @@ -170,25 +167,17 @@ describe('Module token handling', () => { * Tests that providers with different token types (class, string, symbol) are handled correctly. * This ensures the DI system is robust to various token types. */ - it('should support providers with class, string, and symbol tokens', () => { - const StringToken = 'STRING_TOKEN'; + it('should support providers with class and symbol tokens', () => { const SymbolToken = Symbol('SYMBOL_TOKEN'); class ClassToken {} @Module({ providers: [ - { token: StringToken, useValue: 1 }, { token: SymbolToken, useValue: 2 }, { token: ClassToken, useValue: 3 }, ], }) class TokenModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - TokenModule - ); - expect(metadata.providers.some((p: any) => p.token === StringToken)).toBe( - true - ); + const metadata = getMetadata(TokenModule, METADATA_KEYS.MODULE_METADATA); expect(metadata.providers.some((p: any) => p.token === SymbolToken)).toBe( true ); @@ -206,10 +195,7 @@ describe('Module edge cases', () => { it('should handle modules with empty or missing arrays', () => { @Module({}) class EmptyModule {} - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - EmptyModule - ); + const metadata = getMetadata(EmptyModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); expect(metadata.providers ?? []).toBeInstanceOf(Array); expect(metadata.services ?? []).toBeInstanceOf(Array); diff --git a/libs/core/src/module.ts b/libs/core/src/module.ts index 9a3cd74..20c8b15 100644 --- a/libs/core/src/module.ts +++ b/libs/core/src/module.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Module } from './decorators'; import type { ModuleConfig, TokenType } from './types'; -import { METADATA_KEYS } from './types'; +import { METADATA_KEYS } from './constants'; import type { Token } from './token'; +import { SYMBOL_METADATA } from './constants'; +import { setMetadata, getMetadata } from './helpers'; /** * Represents a dynamic module, allowing for runtime configuration of providers and imports. @@ -21,10 +23,7 @@ export abstract class DynamicModule { static getModuleConfig( this: T ): ModuleConfig { - const moduleConfig = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - this - ); + const moduleConfig = getMetadata(this, METADATA_KEYS.MODULE_METADATA); if (!moduleConfig) { throw new Error( `Module ${this.name} is not properly decorated with @Module` @@ -90,7 +89,7 @@ export abstract class DynamicModule { */ static forRoot(config: any): any { class RuntimeDynamicModule {} - Reflect.defineMetadata('nexus:module', config, RuntimeDynamicModule); + setMetadata(RuntimeDynamicModule, METADATA_KEYS.MODULE_METADATA, config); return RuntimeDynamicModule; } } diff --git a/libs/core/src/types.test.ts b/libs/core/src/types.test.ts index 1a4a876..290ab38 100644 --- a/libs/core/src/types.test.ts +++ b/libs/core/src/types.test.ts @@ -2,13 +2,15 @@ import { describe, it, expect } from 'vitest'; import { Token } from './token'; import { type TokenType, - type Provider, + type InjectableToken, type ModuleProvider, type ServiceConfig, type ModuleConfig, - METADATA_KEYS, type InjectionMetadata, } from './types'; +import { getMetadata, setMetadata } from './helpers'; +import { METADATA_KEYS } from './constants'; +import { Inject, Service, Module, Provider } from './decorators'; describe('Types', () => { // TokenType group: Ensures all supported token types are accepted and behave as expected @@ -20,20 +22,10 @@ describe('Types', () => { */ it('should accept Token instances', () => { const token = new Token('TEST'); - const tokenType: TokenType = token; + const tokenType: InjectableToken = token; expect(tokenType).toBe(token); }); - /** - * Test: Accepts string tokens as TokenType - * Validates: Type compatibility and value equality - * Value: Allows simple string-based DI tokens for convenience - */ - it('should accept string tokens', () => { - const tokenType: TokenType = 'STRING_TOKEN'; - expect(tokenType).toBe('STRING_TOKEN'); - }); - /** * Test: Accepts symbol tokens as TokenType * Validates: Type compatibility and value equality @@ -41,7 +33,7 @@ describe('Types', () => { */ it('should accept symbol tokens', () => { const symbol = Symbol('SYMBOL_TOKEN'); - const tokenType: TokenType = symbol; + const tokenType: InjectableToken = symbol; expect(tokenType).toBe(symbol); }); @@ -52,7 +44,7 @@ describe('Types', () => { */ it('should accept class constructors', () => { class TestClass {} - const tokenType: TokenType = TestClass; + const tokenType: InjectableToken = TestClass; expect(tokenType).toBe(TestClass); }); }); @@ -66,12 +58,12 @@ describe('Types', () => { */ it('should define a provider with useClass', () => { class TestService {} - const provider: ModuleProvider = { - token: 'TEST_TOKEN', - useClass: TestService, - }; - - expect((provider as any).token).toBe('TEST_TOKEN'); + const provider: { token: InjectableToken; useClass: typeof TestService } = + { + token: new Token('TEST_TOKEN'), + useClass: TestService, + }; + expect((provider as any).token).toBeInstanceOf(Token); expect((provider as any).useClass).toBe(TestService); }); @@ -81,11 +73,10 @@ describe('Types', () => { * Value: Ensures DI can inject static values or configs */ it('should define a provider with useValue', () => { - const provider: ModuleProvider = { + const provider: { token: InjectableToken; useValue: string } = { token: new Token('VALUE_TOKEN'), useValue: 'test value', }; - expect((provider as any).token).toBeInstanceOf(Token); expect((provider as any).useValue).toBe('test value'); }); @@ -97,15 +88,18 @@ describe('Types', () => { */ it('should define a provider with useFactory', () => { const factory = () => 'factory result'; - const provider: ModuleProvider = { - token: 'FACTORY_TOKEN', + const provider: { + token: InjectableToken; + useFactory: () => string; + deps: InjectableToken[]; + } = { + token: new Token('FACTORY_TOKEN'), useFactory: factory, - deps: ['DEP1', 'DEP2'], + deps: [Symbol('DEP1'), Symbol('DEP2')], }; - - expect((provider as any).token).toBe('FACTORY_TOKEN'); + expect((provider as any).token).toBeInstanceOf(Token); expect((provider as any).useFactory).toBe(factory); - expect((provider as any).deps).toEqual(['DEP1', 'DEP2']); + expect((provider as any).deps.length).toBe(2); }); }); @@ -118,11 +112,10 @@ describe('Types', () => { */ it('should define service config with token', () => { const token = new Token('SERVICE_TOKEN'); - const config: ServiceConfig = { + const config: { token: InjectableToken; singleton: boolean } = { token, singleton: true, }; - expect(config.token).toBe(token); expect(config.singleton).toBe(true); }); @@ -153,14 +146,17 @@ describe('Types', () => { class TestService {} class TestModule {} const token = new Token('PROVIDER_TOKEN'); - - const config: ModuleConfig = { + const config: { + imports: any[]; + services: any[]; + providers: { token: InjectableToken; useClass: typeof TestService }[]; + exports: InjectableToken[]; + } = { imports: [TestModule], services: [TestService], providers: [{ token, useClass: TestService }], exports: [token], }; - expect(config.imports).toEqual([TestModule]); expect(config.services).toEqual([TestService]); expect(config.providers).toHaveLength(1); diff --git a/libs/core/src/types.ts b/libs/core/src/types.ts index 9d9a7f0..5da6804 100644 --- a/libs/core/src/types.ts +++ b/libs/core/src/types.ts @@ -8,7 +8,11 @@ import type { Token } from './token'; * * @see https://nexus.js.org/docs/modules/tokens */ -export type TokenType = new (...args: any[]) => T | string | Token; +export type TokenType = new (...args: any[]) => T | symbol | Token; +export type InjectableToken = + | Token + | symbol + | (new (...args: any[]) => T); /** * Provider definition for DI. Use this to register classes, values, or factories. @@ -22,7 +26,7 @@ export type Provider = { useClass?: new (...args: any[]) => T; useValue?: T; useFactory?: (...args: any[]) => T; - deps?: TokenType[]; + deps?: InjectableToken[]; }; /** @@ -34,8 +38,8 @@ export type Provider = { * @see https://nexus.js.org/docs/modules/module-basics */ export type ModuleProvider = - | (Provider & { token: TokenType }) // Full provider object - | (new (...args: any[]) => T); // Service class (uses @Service decorator token) + | (Provider & { token: InjectableToken }) + | (new (...args: any[]) => T); /** * Configuration for a service. Used with @Service and @Provider decorators. @@ -71,18 +75,27 @@ export type ModuleConfig = { * @see https://nexus.js.org/docs/container/nexus-class */ export interface IContainer { - get(token: TokenType): T; - has(token: TokenType): boolean; - resolve(target: new (...args: any[]) => T): T; - set(token: TokenType, provider: Provider): void; - set(token: TokenType, serviceClass: new (...args: any[]) => T): void; + get(token: Token): T; + get(token: symbol): T; + get(token: new (...args: any[]) => T): T; + + has(token: Token): boolean; + has(token: symbol): boolean; + has(token: new (...args: any[]) => any): boolean; + + resolve(token: Token): T; + resolve(token: symbol): T; + resolve(token: new (...args: any[]) => T): T; + + set(token: Token, provider: Provider): void; + set(token: Token, serviceClass: new (...args: any[]) => T): void; + set(token: symbol, provider: Provider): void; + set(token: symbol, serviceClass: new (...args: any[]) => T): void; set(token: new (...args: any[]) => T, provider: Provider): void; set( token: new (...args: any[]) => T, serviceClass: new (...args: any[]) => T ): void; - set(token: string, provider: Provider): void; - set(token: string, serviceClass: new (...args: any[]) => T): void; set(moduleClass: new (...args: any[]) => any): void; set(moduleConfig: { providers?: ModuleProvider[]; @@ -91,26 +104,8 @@ export interface IContainer { exports?: TokenType[]; }): void; set(tokenOrModuleOrConfig: any, providerOrNothing?: any): void; - registerDynamicModule(moduleConfig: { - services?: (new (...args: any[]) => any)[]; - providers?: ModuleProvider[]; - imports?: (new (...args: any[]) => any)[]; - }): void; } -/** - * Metadata keys used internally for decorators and reflection. - * - * @see https://nexus.js.org/docs/modules/module-basics - */ -export const METADATA_KEYS = { - DESIGN_PARAMTYPES: 'design:paramtypes', - DESIGN_TYPE: 'design:type', - INJECT_METADATA: 'nexusdi:inject', - SERVICE_METADATA: 'nexusdi:service', - MODULE_METADATA: 'nexusdi:module', -} as const; - /** * Metadata for injection, used internally by @Inject and @Optional. * diff --git a/libs/core/tsconfig.lib.json b/libs/core/tsconfig.lib.json index d30b75b..19ac478 100644 --- a/libs/core/tsconfig.lib.json +++ b/libs/core/tsconfig.lib.json @@ -7,7 +7,8 @@ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", "emitDeclarationOnly": false, "forceConsistentCasingInFileNames": true, - "types": ["node"] + "types": ["node"], + "lib": ["es2022", "esnext.decorators"] }, "include": ["src/**/*.ts"], "references": [], From 31cd8b8ff8aa92e3e9ad727ad4fe92cc661c6c42 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 14:07:58 +0200 Subject: [PATCH 02/10] style: cleanup types and unused imports --- libs/core/src/container.ts | 8 +------- libs/core/src/guards.ts | 35 +++++++++++++++++++---------------- libs/core/src/helpers.ts | 2 +- libs/core/src/index.ts | 3 +++ libs/core/src/module.test.ts | 3 --- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index db9a362..c8e6a4b 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -9,13 +9,7 @@ import type { import { Token } from './token'; import { METADATA_KEYS } from './constants'; import { getMetadata } from './helpers'; -import { - isTokenType, - isProvider, - isFactory, - isService, - isContainer, -} from './guards'; +import { isTokenType, isProvider, isService } from './guards'; /** * The main DI container class for NexusDI. Use this to bootstrap and resolve your modules and services. diff --git a/libs/core/src/guards.ts b/libs/core/src/guards.ts index c374c9e..6f8ce50 100644 --- a/libs/core/src/guards.ts +++ b/libs/core/src/guards.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ // Type guards and validators for NexusDI public API import type { TokenType, Provider, IContainer } from './types'; import { Token } from './token'; @@ -5,19 +6,21 @@ import { Token } from './token'; /** * Checks if a value is a Token instance. */ -export function isToken(token: any): token is Token { +export function isToken(token: unknown): token is Token { return !!( token && typeof token === 'object' && - token.constructor && - token.constructor.name === 'Token' + (token as any).constructor && + (token as any).constructor.name === 'Token' ); } /** * Checks if a value is a valid TokenType (class constructor, symbol, or Token instance). */ -export function isTokenType(token: any): token is TokenType { +export function isTokenType( + token: unknown +): token is TokenType { return ( typeof token === 'function' || typeof token === 'symbol' || isToken(token) ); @@ -26,7 +29,7 @@ export function isTokenType(token: any): token is TokenType { /** * Checks if a value is a Provider object (has useClass, useValue, or useFactory). */ -export function isProvider(obj: any): obj is Provider { +export function isProvider(obj: unknown): obj is Provider { return !!( obj && typeof obj === 'object' && @@ -37,36 +40,36 @@ export function isProvider(obj: any): obj is Provider { /** * Checks if a value is a factory provider (has useFactory). */ -export function isFactory(obj: any): obj is { useFactory: () => unknown } { +export function isFactory(obj: unknown): obj is { useFactory: () => unknown } { return !!( obj && typeof obj === 'object' && - typeof obj.useFactory === 'function' + typeof (obj as any).useFactory === 'function' ); } /** * Checks if a value is a service class (has a constructor, a non-empty name, and a prototype object). */ -export function isService(obj: any): obj is new (...args: any[]) => any { +export function isService(obj: unknown): obj is new (...args: any[]) => any { return ( typeof obj === 'function' && - !!obj.name && - obj.prototype && - typeof obj.prototype === 'object' + !!(obj as any).name && + (obj as any).prototype && + typeof (obj as any).prototype === 'object' ); } /** * Checks if a value is a NexusDI container (implements IContainer interface). */ -export function isContainer(obj: any): obj is IContainer { +export function isContainer(obj: unknown): obj is IContainer { return !!( obj && typeof obj === 'object' && - typeof obj.get === 'function' && - typeof obj.set === 'function' && - typeof obj.has === 'function' && - typeof obj.resolve === 'function' + typeof (obj as any).get === 'function' && + typeof (obj as any).set === 'function' && + typeof (obj as any).has === 'function' && + typeof (obj as any).resolve === 'function' ); } diff --git a/libs/core/src/helpers.ts b/libs/core/src/helpers.ts index d19543c..93614b7 100644 --- a/libs/core/src/helpers.ts +++ b/libs/core/src/helpers.ts @@ -1,5 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ // Helpers for Symbol.metadata access -import { SYMBOL_METADATA } from './constants'; export function setMetadata(target: any, key: string, value: any) { // Ensure the metadata object is own, not inherited diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 4ac4ee5..4753b18 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -1,10 +1,13 @@ // NOTE: This file assumes tsconfig.json includes "lib": ["es2022", "esnext.decorators", ...] // Symbol.metadata constant import { SYMBOL_METADATA, METADATA_KEYS } from './constants'; + // Polyfill for Symbol.metadata +/* eslint-disable @typescript-eslint/no-explicit-any */ if (typeof (Symbol as any).metadata === 'undefined') { (Symbol as any).metadata = Symbol(SYMBOL_METADATA); } +/* eslint-enable @typescript-eslint/no-explicit-any */ // Core exports export { Nexus } from './container'; diff --git a/libs/core/src/module.test.ts b/libs/core/src/module.test.ts index 97e7f37..bacec37 100644 --- a/libs/core/src/module.test.ts +++ b/libs/core/src/module.test.ts @@ -2,7 +2,6 @@ import { Module } from './decorators'; import { DynamicModule } from './module'; import { getMetadata } from './helpers'; import { METADATA_KEYS } from './constants'; -import type { TokenType } from './types'; // Dummy config token for testing const TEST_CONFIG_TOKEN = Symbol('TEST_CONFIG'); @@ -39,8 +38,6 @@ class BasicModule {} @Module({ providers: [class ProviderB {}] }) class ParentModule {} -class InheritedModule extends ParentModule {} - /** * NotDecorated: Used to test error handling when getModuleConfig is called on a class * that is not decorated with @Module. This ensures clear error messages for misconfiguration. From 7c67018431b2099bc05e9e8eb8b191496111cf66 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 15:55:09 +0200 Subject: [PATCH 03/10] refactor: cleanup types and divide up file structure --- libs/core/package.json | 7 +- libs/core/src/container.test.ts | 8 +- libs/core/src/container.ts | 159 +++------- libs/core/src/decorators.test.ts | 266 ----------------- libs/core/src/decorators.ts | 276 ------------------ libs/core/src/decorators/index.ts | 5 + libs/core/src/decorators/inject.test.ts | 35 +++ libs/core/src/decorators/inject.ts | 73 +++++ libs/core/src/decorators/module.test.ts | 53 ++++ libs/core/src/decorators/module.ts | 31 ++ libs/core/src/decorators/optional.ts | 61 ++++ libs/core/src/decorators/provider.test.ts | 12 + libs/core/src/decorators/provider.ts | 33 +++ libs/core/src/decorators/service.test.ts | 29 ++ libs/core/src/decorators/service.ts | 48 +++ .../src/exceptions/container.exception.ts | 5 + libs/core/src/exceptions/index.ts | 6 + .../exceptions/invalid-module.exception.ts | 11 + .../exceptions/invalid-provider.exception.ts | 11 + .../exceptions/invalid-service.exception.ts | 11 + .../src/exceptions/invalid-token.exception.ts | 11 + .../src/exceptions/no-provider.exception.ts | 7 + libs/core/src/guards.test.ts | 6 +- libs/core/src/index.ts | 9 + libs/core/src/module.test.ts | 5 +- libs/core/src/module.ts | 6 +- libs/core/src/token.ts | 2 + libs/core/src/types.test.ts | 74 ++--- libs/core/src/types.ts | 54 ++-- 29 files changed, 553 insertions(+), 761 deletions(-) delete mode 100644 libs/core/src/decorators.test.ts delete mode 100644 libs/core/src/decorators.ts create mode 100644 libs/core/src/decorators/index.ts create mode 100644 libs/core/src/decorators/inject.test.ts create mode 100644 libs/core/src/decorators/inject.ts create mode 100644 libs/core/src/decorators/module.test.ts create mode 100644 libs/core/src/decorators/module.ts create mode 100644 libs/core/src/decorators/optional.ts create mode 100644 libs/core/src/decorators/provider.test.ts create mode 100644 libs/core/src/decorators/provider.ts create mode 100644 libs/core/src/decorators/service.test.ts create mode 100644 libs/core/src/decorators/service.ts create mode 100644 libs/core/src/exceptions/container.exception.ts create mode 100644 libs/core/src/exceptions/index.ts create mode 100644 libs/core/src/exceptions/invalid-module.exception.ts create mode 100644 libs/core/src/exceptions/invalid-provider.exception.ts create mode 100644 libs/core/src/exceptions/invalid-service.exception.ts create mode 100644 libs/core/src/exceptions/invalid-token.exception.ts create mode 100644 libs/core/src/exceptions/no-provider.exception.ts diff --git a/libs/core/package.json b/libs/core/package.json index 409a7b8..90ab61d 100644 --- a/libs/core/package.json +++ b/libs/core/package.json @@ -26,12 +26,9 @@ "name": "core" }, "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.6.0" }, "devDependencies": { - "tslib": "^2.8.1" - }, - "peerDependencies": { - "reflect-metadata": "^0.2.2" + "tslib": "^2.6.0" } } diff --git a/libs/core/src/container.test.ts b/libs/core/src/container.test.ts index e24c450..097d1e8 100644 --- a/libs/core/src/container.test.ts +++ b/libs/core/src/container.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { Nexus } from './container'; import { Inject, Service, Module, Provider } from './decorators'; import { Token } from './token'; -import { InvalidToken } from './container'; +import { InvalidToken, InvalidService, NoProvider } from './exceptions'; describe('Nexus', () => { let nexus: Nexus; @@ -58,9 +58,7 @@ describe('Nexus', () => { */ it('should throw error for unregistered token', () => { class Unregistered {} - expect(() => nexus.get(Unregistered)).toThrow( - 'No provider found for token: Unregistered' - ); + expect(() => nexus.get(Unregistered)).toThrowError(NoProvider); }); /** * Test: Token registration check @@ -405,7 +403,7 @@ describe('Nexus', () => { class NotAService {} @Module({ services: [NotAService] }) class Mod {} - expect(() => nexus.set(Mod)).toThrow(/is not decorated with @Service/); + expect(() => nexus.set(Mod)).toThrowError(InvalidService); }); }); diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index c8e6a4b..2cfc96d 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -5,11 +5,18 @@ import type { IContainer, InjectionMetadata, ModuleProvider, + Constructor, } from './types'; -import { Token } from './token'; import { METADATA_KEYS } from './constants'; import { getMetadata } from './helpers'; import { isTokenType, isProvider, isService } from './guards'; +import { + InvalidToken, + NoProvider, + InvalidProvider, + InvalidModule, + InvalidService, +} from './exceptions'; /** * The main DI container class for NexusDI. Use this to bootstrap and resolve your modules and services. @@ -23,9 +30,9 @@ import { isTokenType, isProvider, isService } from './guards'; export class Nexus implements IContainer { private providers = new Map(); private instances = new Map(); - private modules = new Set any>(); + private modules = new Set>(); private aliases = new Map(); - private inProgressModules = new Set any>(); + private inProgressModules = new Set>(); /** * Get an instance of a service or provider by token. @@ -36,36 +43,22 @@ export class Nexus implements IContainer { * const logger = container.get(LoggerService); * @see https://nexus.js.org/docs/container/nexus-class */ - get(token: Token): T; - get(token: symbol): T; - get(token: new (...args: any[]) => T): T; - get(token: any): T { + get(token: TokenType): T { if (!isTokenType(token)) { throw new InvalidToken(token); } - // Resolve aliases const actualToken = this.aliases.get(token) || token; if (!this.has(actualToken)) { - throw new Error( - `No provider found for token: ${this.tokenToString(token)}` - ); + throw new NoProvider(token); } - const provider = this.providers.get(actualToken); if (!provider) { - throw new Error( - `No provider found for token: ${this.tokenToString(token)}` - ); + throw new NoProvider(token); } - - // Return singleton instance if exists if (this.instances.has(actualToken)) { return this.instances.get(actualToken); } - - // Create new instance let instance: T; - if (provider.useValue !== undefined) { instance = provider.useValue; } else if (provider.useFactory) { @@ -74,14 +67,11 @@ export class Nexus implements IContainer { } else if (provider.useClass) { instance = this.resolve(provider.useClass); } else { - throw new Error( + throw new InvalidProvider( `Invalid provider configuration for token: ${this.tokenToString(token)}` ); } - - // Store singleton instance for all provider types this.instances.set(actualToken, instance); - return instance; } @@ -91,14 +81,11 @@ export class Nexus implements IContainer { * @returns True if registered * @see https://nexus.js.org/docs/container/nexus-class */ - has(token: Token): boolean; - has(token: symbol): boolean; - has(token: new (...args: any[]) => any): boolean; - has(token: any): boolean { + has(token: TokenType): boolean { const actualToken = this.aliases.get(token) || token; return ( this.providers.has(actualToken) || - this.modules.has(actualToken as new (...args: any[]) => any) + this.modules.has(actualToken as Constructor) ); } @@ -108,10 +95,7 @@ export class Nexus implements IContainer { * @returns The resolved instance * @see https://nexus.js.org/docs/container/nexus-class */ - resolve(token: Token): T; - resolve(token: symbol): T; - resolve(token: new (...args: any[]) => T): T; - resolve(token: any): T { + resolve(token: TokenType): T { if (!isTokenType(token)) { throw new InvalidToken(token); } @@ -119,75 +103,56 @@ export class Nexus implements IContainer { getMetadata(token, METADATA_KEYS.DESIGN_PARAMTYPES) || []; const ctorInjectionMetadata: InjectionMetadata[] = getMetadata(token, METADATA_KEYS.INJECT_METADATA) || []; - const propInjectionMetadata: InjectionMetadata[] = - getMetadata(token.prototype, METADATA_KEYS.INJECT_METADATA) || []; - - // Create parameter array for constructor + let propInjectionMetadata: InjectionMetadata[] = []; + if (typeof token === 'function') { + propInjectionMetadata = + getMetadata(token.prototype, METADATA_KEYS.INJECT_METADATA) || []; + } const params: any[] = new Array(paramTypes.length); - - // Fill in injected dependencies for constructor for (const metadata of ctorInjectionMetadata) { if (metadata.propertyKey === undefined) { - // Constructor parameter injection params[metadata.index] = this.get(metadata.token); } } - - // Fill remaining parameters with resolved dependencies for (let i = 0; i < paramTypes.length; i++) { if (params[i] === undefined) { const paramType = paramTypes[i]; if (paramType && paramType !== Object) { - // Try to get the dependency by type if (this.has(paramType)) { params[i] = this.get(paramType); } else { - // If not registered, try to resolve it directly params[i] = this.resolve(paramType); } } } } - - // Create instance if (typeof token !== 'function') { - throw new Error( + throw new InvalidToken( `Cannot instantiate non-class token: ${this.tokenToString(token)}` ); } - const instance = new (token as new (...args: any[]) => T)(...params); - - // Handle property injection (from prototype metadata) + const instance = new (token as Constructor)(...params); for (const metadata of propInjectionMetadata) { if (metadata.propertyKey !== undefined) { (instance as any)[metadata.propertyKey] = this.get(metadata.token); } } - return instance; } /** * Unified set method: register a provider, module, or dynamic module config. */ - set(token: Token, provider: Provider): void; - set(token: Token, serviceClass: new (...args: any[]) => T): void; - set(token: symbol, provider: Provider): void; - set(token: symbol, serviceClass: new (...args: any[]) => T): void; - set(token: new (...args: any[]) => T, provider: Provider): void; - set( - token: new (...args: any[]) => T, - serviceClass: new (...args: any[]) => T - ): void; - set(moduleClass: new (...args: any[]) => any): void; + set(token: TokenType, provider: Provider): void; + set(token: TokenType, serviceClass: Constructor): void; + set(moduleClass: Constructor): void; set(moduleConfig: { providers?: ModuleProvider[]; - imports?: (new (...args: any[]) => any)[]; - services?: (new (...args: any[]) => any)[]; + imports?: Constructor[]; + services?: Constructor[]; exports?: TokenType[]; }): void; set(tokenOrModuleOrConfig: any, providerOrNothing?: any): void { - // If it's a module class (has @Module metadata) if ( typeof tokenOrModuleOrConfig === 'function' && getMetadata(tokenOrModuleOrConfig, METADATA_KEYS.MODULE_METADATA) @@ -196,7 +161,7 @@ export class Nexus implements IContainer { return; } if (this.modules.has(tokenOrModuleOrConfig)) { - return; // Module already registered + return; } this.inProgressModules.add(tokenOrModuleOrConfig); this.modules.add(tokenOrModuleOrConfig); @@ -206,15 +171,12 @@ export class Nexus implements IContainer { ); if (!moduleConfig) { this.inProgressModules.delete(tokenOrModuleOrConfig); - throw new Error( - `Module ${tokenOrModuleOrConfig.name} is not properly decorated with @Module` - ); + throw new InvalidModule(tokenOrModuleOrConfig); } this.processModuleConfig(moduleConfig); this.inProgressModules.delete(tokenOrModuleOrConfig); return; } - // If it's a dynamic module config (object with services/providers/imports) if ( tokenOrModuleOrConfig && typeof tokenOrModuleOrConfig === 'object' && @@ -225,21 +187,20 @@ export class Nexus implements IContainer { this.processModuleConfig(tokenOrModuleOrConfig); return; } - // Otherwise, treat as provider registration this.setProvider(tokenOrModuleOrConfig, providerOrNothing); } /** * Internal: Register a provider (class, value, or factory) for a token. */ - private setProvider( - token: TokenType, - providerOrClass: Provider | (new (...args: any[]) => any) + private setProvider( + token: TokenType, + providerOrClass: Provider | Constructor ): void { if (!isTokenType(token)) { throw new InvalidToken(token); } - let provider: Provider; + let provider: Provider; if (typeof providerOrClass === 'function') { provider = { useClass: providerOrClass }; } else { @@ -247,7 +208,6 @@ export class Nexus implements IContainer { } this.providers.set(token, provider); this.instances.delete(token); - // Clear all aliases that point to this token for (const [alias, target] of this.aliases.entries()) { if (target === token) { this.instances.delete(alias); @@ -263,18 +223,15 @@ export class Nexus implements IContainer { * Process module configuration (shared between setModule and registerDynamicModule) */ private processModuleConfig(moduleConfig: { - services?: (new (...args: any[]) => any)[]; + services?: Constructor[]; providers?: ModuleProvider[]; - imports?: (new (...args: any[]) => any)[]; + imports?: Constructor[]; }): void { - // Register imported modules if (moduleConfig.imports) { for (const importedModule of moduleConfig.imports) { this.set(importedModule); } } - - // Register services if (moduleConfig.services) { for (const serviceClass of moduleConfig.services) { const serviceConfig = getMetadata( @@ -282,20 +239,15 @@ export class Nexus implements IContainer { METADATA_KEYS.SERVICE_METADATA ); if (!serviceConfig) { - throw new Error( - `Service class ${serviceClass.name} is not decorated with @Service` - ); + throw new InvalidService(serviceClass); } this.set(serviceConfig.token as TokenType, { useClass: serviceClass, }); } } - - // Register providers if (moduleConfig.providers) { for (const provider of moduleConfig.providers) { - // Check if provider is a service class (has @Service decorator) if (isService(provider)) { const serviceConfig = getMetadata( provider, @@ -306,15 +258,12 @@ export class Nexus implements IContainer { useClass: provider, }); } else { - throw new Error( - `Service class ${provider.name} is not decorated with @Service` - ); + throw new InvalidService(provider); } } else if (isProvider(provider)) { - // Full provider object this.set(provider.token as TokenType, provider); } else { - throw new Error('Invalid provider type'); + throw new InvalidProvider('Invalid provider type'); } } } @@ -325,8 +274,6 @@ export class Nexus implements IContainer { */ createChildContainer(): Nexus { const child = new Nexus(); - - // Copy providers and instances for (const [token, provider] of this.providers) { child.providers.set(token, provider); } @@ -356,17 +303,13 @@ export class Nexus implements IContainer { * Convert a token to a string representation for error messages */ private tokenToString(token: TokenType): string { - if (token instanceof Token) { + if (typeof token === 'object' && token && 'toString' in token) { return token.toString(); - } else if (typeof token === 'string') { - return token; } else if (typeof token === 'symbol') { - // TypeScript sometimes thinks token is never here, so cast to object - return (token as object).toString(); + return (token as unknown as object).toString(); } else if (typeof token === 'function') { return token.name || 'Function'; } - // Fallback for unknown/never types return String(token); } @@ -380,21 +323,3 @@ export class Nexus implements IContainer { }; } } - -export class ContainerException extends Error { - constructor(message: string) { - super(message); - this.name = 'ContainerException'; - } -} - -export class InvalidToken extends ContainerException { - constructor(token: any) { - super( - `Invalid token: ${String( - token - )}. Only class constructors, symbols, or Token instances are allowed as tokens.` - ); - this.name = 'InvalidToken'; - } -} diff --git a/libs/core/src/decorators.test.ts b/libs/core/src/decorators.test.ts deleted file mode 100644 index 061006c..0000000 --- a/libs/core/src/decorators.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { Module, Service, Provider, Inject } from './decorators'; -import { Token } from './token'; -import { getMetadata } from './helpers'; -import { METADATA_KEYS } from './constants'; - -describe('Decorators', () => { - beforeEach(() => { - // Clear metadata before each test - only clear what exists - if ((class TestModule {} as any)[(Symbol as any).metadata]) - delete (class TestModule {} as any)[(Symbol as any).metadata][ - METADATA_KEYS.MODULE_METADATA - ]; - if ((class TestService {} as any)[(Symbol as any).metadata]) - delete (class TestService {} as any)[(Symbol as any).metadata][ - METADATA_KEYS.SERVICE_METADATA - ]; - if ((class TestService {} as any)[(Symbol as any).metadata]) - delete (class TestService {} as any)[(Symbol as any).metadata][ - METADATA_KEYS.INJECT_METADATA - ]; - if ((class TestService {}.prototype as any)[(Symbol as any).metadata]) - delete (class TestService {}.prototype as any)[(Symbol as any).metadata][ - METADATA_KEYS.INJECT_METADATA - ]; - }); - - // @Module decorator group: Ensures module metadata is attached and correct - describe('@Module', () => { - /** - * Test: Adds module metadata to class - * Validates: All properties are present and correct - * Value: Ensures modules are discoverable and configurable by DI - */ - it('should add module metadata to class', () => { - @Module({ - imports: [], - services: [], - providers: [], - exports: [], - }) - class TestModule {} - - const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); - expect(metadata).toEqual({ - imports: [], - services: [], - providers: [], - exports: [], - }); - }); - - /** - * Test: Handles module with services - * Validates: Services array is present and correct - * Value: Ensures service registration in modules works as expected - */ - it('should handle module with services', () => { - class TestService {} - - @Module({ - services: [TestService], - }) - class TestModule {} - - const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); - expect(metadata.services).toEqual([TestService]); - }); - - /** - * Test: Handles module with providers - * Validates: Providers array is present and correct - * Value: Ensures provider registration in modules works as expected - */ - it('should handle module with providers', () => { - const TEST_TOKEN = new Token('TEST'); - - @Module({ - providers: [{ token: TEST_TOKEN, useClass: class TestProvider {} }], - }) - class TestModule {} - - const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); - expect(metadata.providers).toHaveLength(1); - expect(metadata.providers[0].token).toBe(TEST_TOKEN); - }); - - /** - * Test: Handles module with imports - * Validates: Imports array is present and correct - * Value: Ensures module composition and dependency graphing works - */ - it('should handle module with imports', () => { - class ImportedModule {} - - @Module({ - imports: [ImportedModule], - }) - class TestModule {} - - const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); - expect(metadata.imports).toEqual([ImportedModule]); - }); - }); - - // @Service decorator group: Ensures service metadata is attached and correct - describe('@Service', () => { - /** - * Test: Adds service metadata with class as token - * Validates: Token is the class itself - * Value: Ensures default service registration is robust - */ - it('should add service metadata with class as token', () => { - @Service() - class TestService {} - - const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); - expect(metadata.token).toBe(TestService); - }); - - /** - * Test: Adds service metadata with custom token - * Validates: Token is the custom token - * Value: Allows for advanced DI scenarios with custom tokens - */ - it('should add service metadata with custom token', () => { - const customToken = new Token('CUSTOM_SERVICE'); - - @Service(customToken) - class TestService {} - - const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); - expect(metadata.token).toBe(customToken); - }); - }); - - // @Provider decorator group: Ensures provider classes are marked for DI - describe('@Provider', () => { - /** - * Test: Adds provider metadata (marker only) - * Validates: Class is defined and usable - * Value: Ensures provider classes can be registered and discovered - */ - it('should add provider metadata', () => { - const providerToken = new Token('PROVIDER'); - - @Provider(providerToken) - class TestProvider {} - - // @Provider doesn't add metadata, it's just a marker decorator - // The actual provider registration happens in the container - expect(TestProvider).toBeDefined(); - }); - }); - - // @Inject decorator group: Ensures injection metadata is attached and correct - describe('@Inject', () => { - /** - * Test: Adds injection metadata for constructor parameter - * Validates: Metadata is present and correct for parameter - * Value: Enables constructor injection for DI - */ - it('should add injection metadata for constructor parameter', () => { - const injectToken = new Token('INJECT'); - - class TestService { - constructor(@Inject(injectToken) dependency: any) {} - } - - const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); - expect(metadata).toHaveLength(1); - expect(metadata[0].token).toBe(injectToken); - expect(metadata[0].index).toBe(0); - expect(metadata[0].propertyKey).toBeUndefined(); - }); - - /** - * Test: Handles multiple constructor injections - * Validates: Metadata is present and correct for all parameters - * Value: Enables multi-parameter injection for complex services - */ - it('should handle multiple constructor injections', () => { - const token1 = new Token('TOKEN1'); - const token2 = new Token('TOKEN2'); - - class TestService { - constructor(@Inject(token1) dep1: any, @Inject(token2) dep2: any) {} - } - - const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); - expect(metadata).toHaveLength(2); - - // Check that both tokens are present, but don't assume order - const tokens = metadata.map((m: any) => m.token); - expect(tokens).toContain(token1); - expect(tokens).toContain(token2); - - // Check indices - const indices = metadata.map((m: any) => m.index); - expect(indices).toContain(0); - expect(indices).toContain(1); - }); - }); - - // Metadata integration group: Ensures decorators work together for full DI setup - describe('Metadata integration', () => { - /** - * Test: Complete service setup - * Validates: Service and injection metadata are present and correct - * Value: Ensures decorators can be composed for real-world DI scenarios - */ - it('should work with complete service setup', () => { - const serviceToken = new Token('SERVICE'); - const dependencyToken = new Token('DEPENDENCY'); - - @Service(serviceToken) - class TestService { - constructor(@Inject(dependencyToken) dep: any) {} - } - - const serviceMetadata = getMetadata( - TestService, - METADATA_KEYS.SERVICE_METADATA - ); - const injectMetadata = getMetadata( - TestService, - METADATA_KEYS.INJECT_METADATA - ); - - expect(serviceMetadata.token).toBe(serviceToken); - expect(injectMetadata).toHaveLength(1); - expect(injectMetadata[0].token).toBe(dependencyToken); - }); - - /** - * Test: Complete module setup - * Validates: Module and service metadata are present and correct - * Value: Ensures modules and services can be composed for application DI - */ - it('should work with complete module setup', () => { - const SERVICE_TOKEN = new Token('MODULE_SERVICE'); - - @Service(SERVICE_TOKEN) - class ModuleService {} - - @Module({ - services: [ModuleService], - providers: [{ token: new Token('CONFIG'), useValue: 'config' }], - }) - class TestModule {} - - const moduleMetadata = getMetadata( - TestModule, - METADATA_KEYS.MODULE_METADATA - ); - const serviceMetadata = getMetadata( - ModuleService, - METADATA_KEYS.SERVICE_METADATA - ); - - expect(moduleMetadata.services).toEqual([ModuleService]); - expect(moduleMetadata.providers).toHaveLength(1); - expect(serviceMetadata.token).toBe(SERVICE_TOKEN); - }); - }); -}); diff --git a/libs/core/src/decorators.ts b/libs/core/src/decorators.ts deleted file mode 100644 index 5a23545..0000000 --- a/libs/core/src/decorators.ts +++ /dev/null @@ -1,276 +0,0 @@ -// @ts-nocheck -// TypeScript's type system cannot express decorator overloads with union implementation signatures for DI ergonomics. -// This file disables type checking to allow ergonomic and type-safe decorator APIs for users. -// See: https://github.com/microsoft/TypeScript/issues/37181 for context on why this is necessary. -import type { TokenType, ServiceConfig, ModuleConfig } from './types'; -import { METADATA_KEYS } from './constants'; -import type { Token } from './token'; -import { SYMBOL_METADATA } from './constants'; -import { setMetadata, getMetadata } from './helpers'; - -/** - * Decorator that marks a class as a DI module, allowing you to group providers, services, and imports. - * - * Use this to define a module in NexusDI. Modules can import other modules, provide services, and export tokens. - * - * #### Usage - * ```typescript - * import { Module } from '@nexusdi/core'; - * - * @Module({ - * providers: [LoggerService], - * imports: [OtherModule], - * }) - * class AppModule {} - * ``` - * - * @param config The module configuration (providers, imports, exports, etc.) - * - * @see https://nexus.js.org/docs/modules/module-basics - * @see https://nexus.js.org/docs/modules/module-patterns - * @publicApi - */ -export function Module(config: ModuleConfig) { - return (target: new (...args: any[]) => any) => { - setMetadata(target, METADATA_KEYS.MODULE_METADATA, config); - }; -} - -/** - * Decorator that marks a class as a service for dependency injection. - * - * Use this to register a class as a singleton or transient service in the DI container. - * - * #### Usage - * ```typescript - * import { Service } from '@nexusdi/core'; - * - * @Service() - * class LoggerService {} - * ``` - * - * @param token Optional custom token for the service (class, string, or Token) - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @publicApi - */ -export function Service(token?: new (...args: any[]) => T): ClassDecorator; -export function Service(token?: string): ClassDecorator; -export function Service(token?: Token): ClassDecorator; - -// @ts-expect-error: Implementation signature must support all overloads (TypeScript limitation) -export function Service( - token?: new (...args: any[]) => T | string | Token -): ClassDecorator { - return (target: any) => { - const config: ServiceConfig = { - token: token || (target as TokenType), - }; - setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); - }; -} - -/** - * Decorator that marks a class as a provider with a custom token. - * - * Use this for advanced scenarios where you want to register a class under a specific token. - * - * #### Usage - * ```typescript - * import { Provider, Token } from '@nexusdi/core'; - * - * const MY_TOKEN = new Token('MyToken'); - * - * @Provider(MY_TOKEN) - * class MyProvider {} - * ``` - * - * @param token The custom token for the provider (class, string, or Token) - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @see https://nexus.js.org/docs/modules/tokens - * @publicApi - */ -export function Provider(token: new (...args: any[]) => T): ClassDecorator; -export function Provider(token: string): ClassDecorator; -export function Provider(token: Token): ClassDecorator; - -// @ts-expect-error: Implementation signature must support all overloads (TypeScript limitation) -export function Provider( - token: new (...args: any[]) => T | string | Token -): ClassDecorator { - return (target: any) => { - const config: ServiceConfig = { - token, - }; - setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); - }; -} - -/** - * Decorator that marks a constructor parameter or property for dependency injection. - * - * Use this to inject a service, provider, or value into a class. The token can be a class, string, or a custom `Token`. - * - * #### Injection tokens - * - Can be a class (type), string, or a `Token` instance. - * - The token must match a provider registered in the current module or its imports. - * - * #### Usage - * ```typescript - * import { Inject, Token } from '@nexusdi/core'; - * - * const MY_TOKEN = new Token('MyToken'); - * - * class MyService { - * constructor(@Inject(MY_TOKEN) private value: string) {} - * } - * ``` - * - * @param token The lookup key for the provider to be injected (class, string, or Token). - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @see https://nexus.js.org/docs/modules/tokens - * @publicApi - */ -export function Inject( - token: new (...args: any[]) => T -): PropertyDecorator & ParameterDecorator; -export function Inject( - token: string -): PropertyDecorator & ParameterDecorator; -export function Inject( - token: Token -): PropertyDecorator & ParameterDecorator; - -// @ts-expect-error: Implementation signature must support all overloads (TypeScript limitation) -export function Inject( - token: new (...args: any[]) => T | string | Token -): PropertyDecorator & ParameterDecorator { - return ( - target: object, - propertyKey: string | symbol | undefined, - parameterIndex?: number - ) => { - if (typeof parameterIndex === 'number') { - // Parameter decorator - const metadataTarget = target; - const existingMetadata: InjectionMetadata[] = - getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; - const metadata: InjectionMetadata = { - token, - index: parameterIndex, - propertyKey: undefined, - }; - existingMetadata.push(metadata); - setMetadata( - metadataTarget, - METADATA_KEYS.INJECT_METADATA, - existingMetadata - ); - } else if (propertyKey !== undefined) { - // Property decorator - const metadataTarget = target; - const existingMetadata: InjectionMetadata[] = - getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; - const metadata: InjectionMetadata = { - token, - index: 0, - propertyKey, - }; - existingMetadata.push(metadata); - setMetadata( - metadataTarget, - METADATA_KEYS.INJECT_METADATA, - existingMetadata - ); - } - }; -} - -/** - * Decorator that marks a class as injectable for dependency injection. - * - * This is an alias for `@Service()` and is provided for compatibility and clarity. - * - * #### Usage - * ```typescript - * import { Injectable } from '@nexusdi/core'; - * - * @Injectable() - * class MyService {} - * ``` - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @publicApi - */ -export const Injectable = Service; - -/** - * Decorator that marks a dependency as optional for injection. - * - * Use this to indicate that a dependency is not required and may be undefined if not provided. - * - * #### Usage - * ```typescript - * import { Optional, Token } from '@nexusdi/core'; - * - * const MY_TOKEN = new Token('MyToken'); - * - * class MyService { - * constructor(@Optional(MY_TOKEN) private value?: string) {} - * } - * ``` - * - * @param token The lookup key for the optional provider (class, string, or Token). - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @see https://nexus.js.org/docs/modules/tokens - * @publicApi - */ -export function Optional( - token: new (...args: any[]) => T -): PropertyDecorator & ParameterDecorator; -export function Optional( - token: string -): PropertyDecorator & ParameterDecorator; -export function Optional( - token: Token -): PropertyDecorator & ParameterDecorator; - -// @ts-expect-error: Implementation signature must support all overloads (TypeScript limitation) -export function Optional( - token: new (...args: any[]) => T | string | Token -): PropertyDecorator & ParameterDecorator { - return ( - target: object, - propertyKey: string | symbol | undefined, - parameterIndex?: number - ) => { - if (typeof parameterIndex === 'number') { - // Parameter decorator - const existingMetadata: InjectionMetadata[] = - getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; - const metadata: InjectionMetadata = { - token, - index: parameterIndex, - propertyKey: undefined, - optional: true, - }; - existingMetadata.push(metadata); - setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); - } else if (propertyKey !== undefined) { - // Property decorator - const existingMetadata: InjectionMetadata[] = - getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; - const metadata: InjectionMetadata = { - token, - index: 0, - propertyKey, - optional: true, - }; - existingMetadata.push(metadata); - setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); - } - }; -} diff --git a/libs/core/src/decorators/index.ts b/libs/core/src/decorators/index.ts new file mode 100644 index 0000000..5e3db3f --- /dev/null +++ b/libs/core/src/decorators/index.ts @@ -0,0 +1,5 @@ +export * from './module'; +export * from './service'; +export * from './provider'; +export * from './inject'; +export * from './optional'; diff --git a/libs/core/src/decorators/inject.test.ts b/libs/core/src/decorators/inject.test.ts new file mode 100644 index 0000000..adaea34 --- /dev/null +++ b/libs/core/src/decorators/inject.test.ts @@ -0,0 +1,35 @@ +import { describe, it, expect } from 'vitest'; +import { Inject } from './inject'; +import { Token } from '../token'; +import { getMetadata } from '../helpers'; +import { METADATA_KEYS } from '../constants'; + +describe('@Inject', () => { + it('should add injection metadata for constructor parameter', () => { + const INJECT_TOKEN = new Token('INJECT'); + class TestService { + constructor(@Inject(INJECT_TOKEN) dependency: any) {} + } + const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); + expect(metadata).toHaveLength(1); + expect(metadata[0].token).toBe(INJECT_TOKEN); + expect(metadata[0].index).toBe(0); + expect(metadata[0].propertyKey).toBeUndefined(); + }); + + it('should handle multiple constructor injections', () => { + const TOKEN_ONE = new Token('TOKEN1'); + const TOKEN_TWO = new Token('TOKEN2'); + class TestService { + constructor(@Inject(TOKEN_ONE) dep1: any, @Inject(TOKEN_TWO) dep2: any) {} + } + const metadata = getMetadata(TestService, METADATA_KEYS.INJECT_METADATA); + expect(metadata).toHaveLength(2); + const tokens = metadata.map((m: any) => m.token); + expect(tokens).toContain(TOKEN_ONE); + expect(tokens).toContain(TOKEN_TWO); + const indices = metadata.map((m: any) => m.index); + expect(indices).toContain(0); + expect(indices).toContain(1); + }); +}); diff --git a/libs/core/src/decorators/inject.ts b/libs/core/src/decorators/inject.ts new file mode 100644 index 0000000..fc01f8b --- /dev/null +++ b/libs/core/src/decorators/inject.ts @@ -0,0 +1,73 @@ +import type { InjectionMetadata, TokenType } from '../types'; +import { METADATA_KEYS } from '../constants'; +import { setMetadata, getMetadata } from '../helpers'; + +/** + * Decorator that marks a constructor parameter or property for dependency injection. + * + * Use this to inject a service, provider, or value into a class. The token can be a class constructor, symbol, or a custom `Token`. + * + * #### Injection tokens + * - Can be a class constructor, symbol, or a `Token` instance. + * - The token must match a provider registered in the current module or its imports. + * + * #### Usage + * ```typescript + * import { Inject, Token } from '@nexusdi/core'; + * + * const MY_TOKEN = new Token('MyToken'); + * + * class MyService { + * constructor(@Inject(MY_TOKEN) private value: string) {} + * } + * ``` + * + * @param token The lookup key for the provider to be injected (class constructor, symbol, or Token). + * + * @see https://nexus.js.org/docs/modules/providers-and-services + * @see https://nexus.js.org/docs/modules/tokens + * @publicApi + */ +export function Inject( + token: TokenType +): PropertyDecorator & ParameterDecorator { + return ( + target: object, + propertyKey: string | symbol | undefined, + parameterIndex?: number + ) => { + if (typeof parameterIndex === 'number') { + // Parameter decorator + const metadataTarget = target; + const existingMetadata: InjectionMetadata[] = + getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; + const metadata: InjectionMetadata = { + token, + index: parameterIndex, + propertyKey: undefined, + }; + existingMetadata.push(metadata); + setMetadata( + metadataTarget, + METADATA_KEYS.INJECT_METADATA, + existingMetadata + ); + } else if (propertyKey !== undefined) { + // Property decorator + const metadataTarget = target; + const existingMetadata: InjectionMetadata[] = + getMetadata(metadataTarget, METADATA_KEYS.INJECT_METADATA) || []; + const metadata: InjectionMetadata = { + token, + index: 0, + propertyKey, + }; + existingMetadata.push(metadata); + setMetadata( + metadataTarget, + METADATA_KEYS.INJECT_METADATA, + existingMetadata + ); + } + }; +} diff --git a/libs/core/src/decorators/module.test.ts b/libs/core/src/decorators/module.test.ts new file mode 100644 index 0000000..00df177 --- /dev/null +++ b/libs/core/src/decorators/module.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { Module } from './module'; +import { Token } from '../token'; +import { getMetadata } from '../helpers'; +import { METADATA_KEYS } from '../constants'; + +describe('@Module', () => { + beforeEach(() => { + if ((class TestModule {} as any)[(Symbol as any).metadata]) + delete (class TestModule {} as any)[(Symbol as any).metadata][ + METADATA_KEYS.MODULE_METADATA + ]; + }); + + it('should add module metadata to class', () => { + @Module({ imports: [], services: [], providers: [], exports: [] }) + class TestModule {} + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); + expect(metadata).toEqual({ + imports: [], + services: [], + providers: [], + exports: [], + }); + }); + + it('should handle module with services', () => { + class TestService {} + @Module({ services: [TestService] }) + class TestModule {} + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); + expect(metadata.services).toEqual([TestService]); + }); + + it('should handle module with providers', () => { + const TEST_TOKEN = new Token('TEST'); + @Module({ + providers: [{ token: TEST_TOKEN, useClass: class TestProvider {} }], + }) + class TestModule {} + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); + expect(metadata.providers).toHaveLength(1); + expect(metadata.providers[0].token).toBe(TEST_TOKEN); + }); + + it('should handle module with imports', () => { + class ImportedModule {} + @Module({ imports: [ImportedModule] }) + class TestModule {} + const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); + expect(metadata.imports).toEqual([ImportedModule]); + }); +}); diff --git a/libs/core/src/decorators/module.ts b/libs/core/src/decorators/module.ts new file mode 100644 index 0000000..12be6bd --- /dev/null +++ b/libs/core/src/decorators/module.ts @@ -0,0 +1,31 @@ +import type { ModuleConfig } from '../types'; +import { METADATA_KEYS } from '../constants'; +import { setMetadata } from '../helpers'; + +/** + * Decorator that marks a class as a DI module, allowing you to group providers, services, and imports. + * + * Use this to define a module in NexusDI. Modules can import other modules, provide services, and export tokens. + * + * #### Usage + * ```typescript + * import { Module } from '@nexusdi/core'; + * + * @Module({ + * providers: [LoggerService], + * imports: [OtherModule], + * }) + * class AppModule {} + * ``` + * + * @param config The module configuration (providers, imports, exports, etc.) + * + * @see https://nexus.js.org/docs/modules/module-basics + * @see https://nexus.js.org/docs/modules/module-patterns + * @publicApi + */ +export function Module(config: ModuleConfig): ClassDecorator { + return (target) => { + setMetadata(target, METADATA_KEYS.MODULE_METADATA, config); + }; +} diff --git a/libs/core/src/decorators/optional.ts b/libs/core/src/decorators/optional.ts new file mode 100644 index 0000000..ce62248 --- /dev/null +++ b/libs/core/src/decorators/optional.ts @@ -0,0 +1,61 @@ +import type { InjectionMetadata, TokenType } from '../types'; +import { METADATA_KEYS } from '../constants'; +import { setMetadata, getMetadata } from '../helpers'; + +/** + * Decorator that marks a dependency as optional for injection. + * + * Use this to indicate that a dependency is not required and may be undefined if not provided. + * + * #### Usage + * ```typescript + * import { Optional, Token } from '@nexusdi/core'; + * + * const MY_TOKEN = new Token('MyToken'); + * + * class MyService { + * constructor(@Optional(MY_TOKEN) private value?: string) {} + * } + * ``` + * + * @param token The lookup key for the optional provider (class constructor, symbol, or Token). + * + * @see https://nexus.js.org/docs/modules/providers-and-services + * @see https://nexus.js.org/docs/modules/tokens + * @publicApi + */ +export function Optional( + token: TokenType +): PropertyDecorator & ParameterDecorator { + return ( + target: object, + propertyKey: string | symbol | undefined, + parameterIndex?: number + ) => { + if (typeof parameterIndex === 'number') { + // Parameter decorator + const existingMetadata: InjectionMetadata[] = + getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; + const metadata: InjectionMetadata = { + token, + index: parameterIndex, + propertyKey: undefined, + optional: true, + }; + existingMetadata.push(metadata); + setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); + } else if (propertyKey !== undefined) { + // Property decorator + const existingMetadata: InjectionMetadata[] = + getMetadata(target, METADATA_KEYS.INJECT_METADATA) || []; + const metadata: InjectionMetadata = { + token, + index: 0, + propertyKey, + optional: true, + }; + existingMetadata.push(metadata); + setMetadata(target, METADATA_KEYS.INJECT_METADATA, existingMetadata); + } + }; +} diff --git a/libs/core/src/decorators/provider.test.ts b/libs/core/src/decorators/provider.test.ts new file mode 100644 index 0000000..7a6a5c9 --- /dev/null +++ b/libs/core/src/decorators/provider.test.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest'; +import { Provider } from './provider'; +import { Token } from '../token'; + +describe('@Provider', () => { + it('should add provider metadata', () => { + const PROVIDER_TOKEN = new Token('PROVIDER'); + @Provider(PROVIDER_TOKEN) + class TestProvider {} + expect(TestProvider).toBeDefined(); + }); +}); diff --git a/libs/core/src/decorators/provider.ts b/libs/core/src/decorators/provider.ts new file mode 100644 index 0000000..5193379 --- /dev/null +++ b/libs/core/src/decorators/provider.ts @@ -0,0 +1,33 @@ +import type { ServiceConfig, TokenType } from '../types'; +import { METADATA_KEYS } from '../constants'; +import { setMetadata } from '../helpers'; + +/** + * Decorator that marks a class as a provider with a custom token. + * + * Use this for advanced scenarios where you want to register a class under a specific token. + * + * #### Usage + * ```typescript + * import { Provider, Token } from '@nexusdi/core'; + * + * const MY_TOKEN = new Token('MyToken'); + * + * @Provider(MY_TOKEN) + * class MyProvider {} + * ``` + * + * @param token The custom token for the provider (class constructor, symbol, or Token) + * + * @see https://nexus.js.org/docs/modules/providers-and-services + * @see https://nexus.js.org/docs/modules/tokens + * @publicApi + */ +export function Provider(token: TokenType): ClassDecorator { + return (target) => { + const config: ServiceConfig = { + token, + }; + setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); + }; +} diff --git a/libs/core/src/decorators/service.test.ts b/libs/core/src/decorators/service.test.ts new file mode 100644 index 0000000..bc77ccc --- /dev/null +++ b/libs/core/src/decorators/service.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { Service } from './service'; +import { Token } from '../token'; +import { getMetadata } from '../helpers'; +import { METADATA_KEYS } from '../constants'; + +describe('@Service', () => { + beforeEach(() => { + if ((class TestService {} as any)[(Symbol as any).metadata]) + delete (class TestService {} as any)[(Symbol as any).metadata][ + METADATA_KEYS.SERVICE_METADATA + ]; + }); + + it('should add service metadata with class as token', () => { + @Service() + class TestService {} + const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); + expect(metadata.token).toBe(TestService); + }); + + it('should add service metadata with custom token', () => { + const CUSTOM_TOKEN = new Token('CUSTOM_SERVICE'); + @Service(CUSTOM_TOKEN) + class TestService {} + const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); + expect(metadata.token).toBe(CUSTOM_TOKEN); + }); +}); diff --git a/libs/core/src/decorators/service.ts b/libs/core/src/decorators/service.ts new file mode 100644 index 0000000..6eceec8 --- /dev/null +++ b/libs/core/src/decorators/service.ts @@ -0,0 +1,48 @@ +import type { TokenType, ServiceConfig } from '../types'; +import { METADATA_KEYS } from '../constants'; +import { setMetadata } from '../helpers'; + +/** + * Decorator that marks a class as a service for dependency injection. + * + * Use this to register a class as a singleton or transient service in the DI container. + * + * #### Usage + * ```typescript + * import { Service } from '@nexusdi/core'; + * + * @Service() + * class LoggerService {} + * ``` + * + * @param token Optional custom token for the service (class constructor, symbol, or Token) + * + * @see https://nexus.js.org/docs/modules/providers-and-services + * @publicApi + */ +export function Service(token?: TokenType): ClassDecorator { + return (target) => { + const config: ServiceConfig = { + token: token || (target as unknown as TokenType), + }; + setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); + }; +} + +/** + * Decorator that marks a class as injectable for dependency injection. + * + * This is an alias for `@Service()` and is provided for compatibility and clarity. + * + * #### Usage + * ```typescript + * import { Injectable } from '@nexusdi/core'; + * + * @Injectable() + * class MyService {} + * ``` + * + * @see https://nexus.js.org/docs/modules/providers-and-services + * @publicApi + */ +export const Injectable = Service; diff --git a/libs/core/src/exceptions/container.exception.ts b/libs/core/src/exceptions/container.exception.ts new file mode 100644 index 0000000..bb9fbbc --- /dev/null +++ b/libs/core/src/exceptions/container.exception.ts @@ -0,0 +1,5 @@ +export class ContainerException extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/libs/core/src/exceptions/index.ts b/libs/core/src/exceptions/index.ts new file mode 100644 index 0000000..d79ec66 --- /dev/null +++ b/libs/core/src/exceptions/index.ts @@ -0,0 +1,6 @@ +export * from './invalid-module.exception'; +export * from './invalid-service.exception'; +export * from './invalid-token.exception'; +export * from './invalid-provider.exception'; +export * from './no-provider.exception'; +export * from './container.exception'; diff --git a/libs/core/src/exceptions/invalid-module.exception.ts b/libs/core/src/exceptions/invalid-module.exception.ts new file mode 100644 index 0000000..65302f5 --- /dev/null +++ b/libs/core/src/exceptions/invalid-module.exception.ts @@ -0,0 +1,11 @@ +import { ContainerException } from './container.exception'; + +export class InvalidModule extends ContainerException { + constructor(module: unknown) { + super( + `Module ${ + typeof module === 'function' ? module.name : String(module) + } is not properly decorated with @Module` + ); + } +} diff --git a/libs/core/src/exceptions/invalid-provider.exception.ts b/libs/core/src/exceptions/invalid-provider.exception.ts new file mode 100644 index 0000000..48f5c79 --- /dev/null +++ b/libs/core/src/exceptions/invalid-provider.exception.ts @@ -0,0 +1,11 @@ +import { ContainerException } from './container.exception'; + +export class InvalidProvider extends ContainerException { + constructor(provider: unknown) { + super( + `Invalid provider: ${String( + provider + )}. Only class constructors, symbols, or Token instances are allowed as providers.` + ); + } +} diff --git a/libs/core/src/exceptions/invalid-service.exception.ts b/libs/core/src/exceptions/invalid-service.exception.ts new file mode 100644 index 0000000..40bb980 --- /dev/null +++ b/libs/core/src/exceptions/invalid-service.exception.ts @@ -0,0 +1,11 @@ +import { ContainerException } from './container.exception'; + +export class InvalidService extends ContainerException { + constructor(service: unknown) { + super( + `Service class ${ + typeof service === 'function' ? service.name : String(service) + } is not decorated with @Service` + ); + } +} diff --git a/libs/core/src/exceptions/invalid-token.exception.ts b/libs/core/src/exceptions/invalid-token.exception.ts new file mode 100644 index 0000000..f4e6709 --- /dev/null +++ b/libs/core/src/exceptions/invalid-token.exception.ts @@ -0,0 +1,11 @@ +import { ContainerException } from './container.exception'; + +export class InvalidToken extends ContainerException { + constructor(token: unknown) { + super( + `Invalid token: ${String( + token + )}. Only class constructors, symbols, or Token instances are allowed as tokens.` + ); + } +} diff --git a/libs/core/src/exceptions/no-provider.exception.ts b/libs/core/src/exceptions/no-provider.exception.ts new file mode 100644 index 0000000..7c0b284 --- /dev/null +++ b/libs/core/src/exceptions/no-provider.exception.ts @@ -0,0 +1,7 @@ +import { ContainerException } from './container.exception'; + +export class NoProvider extends ContainerException { + constructor(token: unknown) { + super(`No provider found for token: ${String(token)}`); + } +} diff --git a/libs/core/src/guards.test.ts b/libs/core/src/guards.test.ts index 9debb37..01d3df2 100644 --- a/libs/core/src/guards.test.ts +++ b/libs/core/src/guards.test.ts @@ -7,7 +7,7 @@ import { isContainer, } from './guards'; import { Token } from './token'; -import type { Provider, IContainer } from './types'; +import type { IContainer } from './types'; /** * Guards: Ensures all public guard functions work as expected for valid and invalid cases @@ -77,12 +77,12 @@ describe('Guards', () => { describe('isContainer', () => { it('should return true for objects implementing IContainer', () => { - const fake: IContainer = { + const fake = { get: () => 1, set: () => {}, has: () => true, resolve: () => 1, - }; + } as unknown as IContainer; expect(isContainer(fake)).toBe(true); }); it('should return false for objects missing required methods', () => { diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 4753b18..3c06c78 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -27,6 +27,15 @@ export { Optional, } from './decorators'; +export { + ContainerException, + InvalidToken, + InvalidService, + NoProvider, + InvalidProvider, + InvalidModule, +} from './exceptions'; + // Dynamic Module export { DynamicModule } from './module'; diff --git a/libs/core/src/module.test.ts b/libs/core/src/module.test.ts index bacec37..2125ca2 100644 --- a/libs/core/src/module.test.ts +++ b/libs/core/src/module.test.ts @@ -2,6 +2,7 @@ import { Module } from './decorators'; import { DynamicModule } from './module'; import { getMetadata } from './helpers'; import { METADATA_KEYS } from './constants'; +import { InvalidModule } from './exceptions/invalid-module.exception'; // Dummy config token for testing const TEST_CONFIG_TOKEN = Symbol('TEST_CONFIG'); @@ -153,9 +154,7 @@ describe('Module error handling', () => { * This ensures clear error messages for misconfiguration and helps developers debug quickly. */ it('should throw if not decorated with @Module', () => { - expect(() => NotDecorated.getModuleConfig()).toThrow( - /not properly decorated/ - ); + expect(() => NotDecorated.getModuleConfig()).toThrowError(InvalidModule); }); }); diff --git a/libs/core/src/module.ts b/libs/core/src/module.ts index 20c8b15..9b8a24c 100644 --- a/libs/core/src/module.ts +++ b/libs/core/src/module.ts @@ -3,8 +3,8 @@ import { Module } from './decorators'; import type { ModuleConfig, TokenType } from './types'; import { METADATA_KEYS } from './constants'; import type { Token } from './token'; -import { SYMBOL_METADATA } from './constants'; import { setMetadata, getMetadata } from './helpers'; +import { InvalidModule } from './exceptions/invalid-module.exception'; /** * Represents a dynamic module, allowing for runtime configuration of providers and imports. @@ -25,9 +25,7 @@ export abstract class DynamicModule { ): ModuleConfig { const moduleConfig = getMetadata(this, METADATA_KEYS.MODULE_METADATA); if (!moduleConfig) { - throw new Error( - `Module ${this.name} is not properly decorated with @Module` - ); + throw new InvalidModule(this); } return moduleConfig; } diff --git a/libs/core/src/token.ts b/libs/core/src/token.ts index 0451195..4047564 100644 --- a/libs/core/src/token.ts +++ b/libs/core/src/token.ts @@ -6,6 +6,8 @@ * @example * import { Token } from '@nexusdi/core'; * export const MY_TOKEN = new Token('MyToken'); + * export const MY_SERVICE = new Token(); + * export const MY_OTHER_SERVICE = new Token(); * @see https://nexus.js.org/docs/modules/tokens */ // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/libs/core/src/types.test.ts b/libs/core/src/types.test.ts index 290ab38..22f3887 100644 --- a/libs/core/src/types.test.ts +++ b/libs/core/src/types.test.ts @@ -2,15 +2,12 @@ import { describe, it, expect } from 'vitest'; import { Token } from './token'; import { type TokenType, - type InjectableToken, type ModuleProvider, type ServiceConfig, type ModuleConfig, type InjectionMetadata, } from './types'; -import { getMetadata, setMetadata } from './helpers'; import { METADATA_KEYS } from './constants'; -import { Inject, Service, Module, Provider } from './decorators'; describe('Types', () => { // TokenType group: Ensures all supported token types are accepted and behave as expected @@ -22,7 +19,7 @@ describe('Types', () => { */ it('should accept Token instances', () => { const token = new Token('TEST'); - const tokenType: InjectableToken = token; + const tokenType: TokenType = token; expect(tokenType).toBe(token); }); @@ -33,7 +30,7 @@ describe('Types', () => { */ it('should accept symbol tokens', () => { const symbol = Symbol('SYMBOL_TOKEN'); - const tokenType: InjectableToken = symbol; + const tokenType: TokenType = symbol; expect(tokenType).toBe(symbol); }); @@ -44,7 +41,7 @@ describe('Types', () => { */ it('should accept class constructors', () => { class TestClass {} - const tokenType: InjectableToken = TestClass; + const tokenType: TokenType = TestClass; expect(tokenType).toBe(TestClass); }); }); @@ -58,13 +55,12 @@ describe('Types', () => { */ it('should define a provider with useClass', () => { class TestService {} - const provider: { token: InjectableToken; useClass: typeof TestService } = - { - token: new Token('TEST_TOKEN'), - useClass: TestService, - }; - expect((provider as any).token).toBeInstanceOf(Token); - expect((provider as any).useClass).toBe(TestService); + const provider: { token: TokenType; useClass: typeof TestService } = { + token: new Token('TEST_TOKEN'), + useClass: TestService, + }; + expect(provider.token).toBeInstanceOf(Token); + expect(provider.useClass).toBe(TestService); }); /** @@ -73,12 +69,12 @@ describe('Types', () => { * Value: Ensures DI can inject static values or configs */ it('should define a provider with useValue', () => { - const provider: { token: InjectableToken; useValue: string } = { + const provider: { token: TokenType; useValue: string } = { token: new Token('VALUE_TOKEN'), useValue: 'test value', }; - expect((provider as any).token).toBeInstanceOf(Token); - expect((provider as any).useValue).toBe('test value'); + expect(provider.token).toBeInstanceOf(Token); + expect(provider.useValue).toBe('test value'); }); /** @@ -89,17 +85,17 @@ describe('Types', () => { it('should define a provider with useFactory', () => { const factory = () => 'factory result'; const provider: { - token: InjectableToken; + token: TokenType; useFactory: () => string; - deps: InjectableToken[]; + deps: TokenType[]; } = { token: new Token('FACTORY_TOKEN'), useFactory: factory, deps: [Symbol('DEP1'), Symbol('DEP2')], }; - expect((provider as any).token).toBeInstanceOf(Token); - expect((provider as any).useFactory).toBe(factory); - expect((provider as any).deps.length).toBe(2); + expect(provider.token).toBeInstanceOf(Token); + expect(provider.useFactory).toBe(factory); + expect(provider.deps.length).toBe(2); }); }); @@ -112,7 +108,7 @@ describe('Types', () => { */ it('should define service config with token', () => { const token = new Token('SERVICE_TOKEN'); - const config: { token: InjectableToken; singleton: boolean } = { + const config: { token: TokenType; singleton: boolean } = { token, singleton: true, }; @@ -149,18 +145,18 @@ describe('Types', () => { const config: { imports: any[]; services: any[]; - providers: { token: InjectableToken; useClass: typeof TestService }[]; - exports: InjectableToken[]; + providers: { token: TokenType; useClass: typeof TestService }[]; + exports: TokenType[]; } = { imports: [TestModule], services: [TestService], providers: [{ token, useClass: TestService }], - exports: [token], + exports: [token as unknown as TokenType], }; expect(config.imports).toEqual([TestModule]); expect(config.services).toEqual([TestService]); expect(config.providers).toHaveLength(1); - expect(config.exports).toEqual([token]); + expect(config.exports).toEqual([token as unknown as TokenType]); }); /** @@ -204,10 +200,9 @@ describe('Types', () => { it('should define injection metadata for constructor parameter', () => { const token = new Token('INJECT_TOKEN'); const metadata: InjectionMetadata = { - token, + token: token as unknown as TokenType, index: 0, }; - expect(metadata.token).toBe(token); expect(metadata.index).toBe(0); expect(metadata.propertyKey).toBeUndefined(); @@ -221,11 +216,10 @@ describe('Types', () => { it('should define injection metadata for property', () => { const token = new Token('PROPERTY_TOKEN'); const metadata: InjectionMetadata = { - token, + token: token as unknown as TokenType, index: 0, propertyKey: 'testProperty', }; - expect(metadata.token).toBe(token); expect(metadata.index).toBe(0); expect(metadata.propertyKey).toBe('testProperty'); @@ -240,11 +234,10 @@ describe('Types', () => { const token = new Token('SYMBOL_PROPERTY'); const symbol = Symbol('symbolProperty'); const metadata: InjectionMetadata = { - token, + token: token as unknown as TokenType, index: 1, propertyKey: symbol, }; - expect(metadata.token).toBe(token); expect(metadata.index).toBe(1); expect(metadata.propertyKey).toBe(symbol); @@ -253,19 +246,6 @@ describe('Types', () => { // Type compatibility group: Ensures types are compatible for advanced DI scenarios describe('Type compatibility', () => { - /** - * Test: TokenType compatibility with Token class - * Validates: Instance and string value - * Value: Ensures TokenType can be used for advanced DI scenarios - */ - it('should be compatible with Token class', () => { - const token = new Token('COMPATIBILITY_TEST'); - const tokenType: TokenType = token; - - expect(tokenType).toBeInstanceOf(Token); - expect(tokenType.toString()).toBe('COMPATIBILITY_TEST'); - }); - /** * Test: Provider registration with TokenType * Validates: Structure and values @@ -291,7 +271,9 @@ describe('Types', () => { */ it('should work with service configuration', () => { class TestService {} - const token = new Token('SERVICE_TOKEN'); + const token = new Token( + 'SERVICE_TOKEN' + ) as unknown as TokenType; const config: ServiceConfig = { token, diff --git a/libs/core/src/types.ts b/libs/core/src/types.ts index 5da6804..96f1738 100644 --- a/libs/core/src/types.ts +++ b/libs/core/src/types.ts @@ -1,18 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Token } from './token'; +export type Constructor = new (...args: any[]) => T; + /** - * [INTERNAL] Represents a token for DI registration. Used internally to support class, string, or Token-based tokens. + * [INTERNAL] Represents a token for DI registration. Used internally to support class, or Token-based tokens. * * Users should use the exported `Token` class for custom tokens instead of Symbol or string. * * @see https://nexus.js.org/docs/modules/tokens */ -export type TokenType = new (...args: any[]) => T | symbol | Token; -export type InjectableToken = - | Token - | symbol - | (new (...args: any[]) => T); +export type TokenType = Token | symbol | Constructor; /** * Provider definition for DI. Use this to register classes, values, or factories. @@ -23,10 +21,10 @@ export type InjectableToken = * @see https://nexus.js.org/docs/modules/providers-and-services */ export type Provider = { - useClass?: new (...args: any[]) => T; + useClass?: Constructor; useValue?: T; useFactory?: (...args: any[]) => T; - deps?: InjectableToken[]; + deps?: TokenType[]; }; /** @@ -38,8 +36,8 @@ export type Provider = { * @see https://nexus.js.org/docs/modules/module-basics */ export type ModuleProvider = - | (Provider & { token: InjectableToken }) - | (new (...args: any[]) => T); + | (Provider & { token: TokenType }) + | Constructor; /** * Configuration for a service. Used with @Service and @Provider decorators. @@ -63,8 +61,8 @@ export type ServiceConfig = { * @see https://nexus.js.org/docs/modules/module-basics */ export type ModuleConfig = { - imports?: (new (...args: any[]) => any)[]; - services?: (new (...args: any[]) => any)[]; + imports?: Constructor[]; + services?: Constructor[]; providers?: ModuleProvider[]; exports?: TokenType[]; }; @@ -75,32 +73,19 @@ export type ModuleConfig = { * @see https://nexus.js.org/docs/container/nexus-class */ export interface IContainer { - get(token: Token): T; - get(token: symbol): T; - get(token: new (...args: any[]) => T): T; + get(token: TokenType): T; - has(token: Token): boolean; - has(token: symbol): boolean; - has(token: new (...args: any[]) => any): boolean; + has(token: TokenType): boolean; - resolve(token: Token): T; - resolve(token: symbol): T; - resolve(token: new (...args: any[]) => T): T; + resolve(token: TokenType): T; - set(token: Token, provider: Provider): void; - set(token: Token, serviceClass: new (...args: any[]) => T): void; - set(token: symbol, provider: Provider): void; - set(token: symbol, serviceClass: new (...args: any[]) => T): void; - set(token: new (...args: any[]) => T, provider: Provider): void; - set( - token: new (...args: any[]) => T, - serviceClass: new (...args: any[]) => T - ): void; - set(moduleClass: new (...args: any[]) => any): void; + set(token: TokenType, provider: Provider): void; + set(token: TokenType, serviceClass: Constructor): void; + set(moduleClass: Constructor): void; set(moduleConfig: { providers?: ModuleProvider[]; - imports?: (new (...args: any[]) => any)[]; - services?: (new (...args: any[]) => any)[]; + imports?: Constructor[]; + services?: Constructor[]; exports?: TokenType[]; }): void; set(tokenOrModuleOrConfig: any, providerOrNothing?: any): void; @@ -117,6 +102,3 @@ export type InjectionMetadata = { propertyKey?: string | symbol; optional?: boolean; }; - -// Re-export Token class for convenience -export { Token } from './token'; From 23cf191bf551ac4127e62d79e2ea1b51750a7328 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 18:39:29 +0200 Subject: [PATCH 04/10] refactor: unify service and provider decorators #20 --- libs/core/src/constants.ts | 1 + libs/core/src/container.ts | 23 +++++--- libs/core/src/decorators/index.ts | 1 - libs/core/src/decorators/provider.test.ts | 12 ---- libs/core/src/decorators/provider.ts | 33 ----------- libs/core/src/decorators/service.test.ts | 15 ++++- libs/core/src/decorators/service.ts | 69 +++++++++++----------- libs/core/src/index.ts | 11 +--- libs/core/src/types.test.ts | 72 ++++++++--------------- libs/core/src/types.ts | 44 ++++++++++++-- libs/core/vite.config.ts | 7 +++ 11 files changed, 137 insertions(+), 151 deletions(-) delete mode 100644 libs/core/src/decorators/provider.test.ts delete mode 100644 libs/core/src/decorators/provider.ts diff --git a/libs/core/src/constants.ts b/libs/core/src/constants.ts index 97cafd6..f66f92a 100644 --- a/libs/core/src/constants.ts +++ b/libs/core/src/constants.ts @@ -7,5 +7,6 @@ export const METADATA_KEYS = { DESIGN_TYPE: 'design:type', INJECT_METADATA: 'nexusdi:inject', SERVICE_METADATA: 'nexusdi:service', + PROVIDER_METADATA: 'nexusdi:provider', MODULE_METADATA: 'nexusdi:module', } as const; diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index 2cfc96d..653eb81 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -202,6 +202,13 @@ export class Nexus implements IContainer { } let provider: Provider; if (typeof providerOrClass === 'function') { + const providerConfig = getMetadata( + providerOrClass, + METADATA_KEYS.PROVIDER_METADATA + ); + if (!providerConfig) { + throw new InvalidService(providerOrClass); + } provider = { useClass: providerOrClass }; } else { provider = providerOrClass; @@ -234,14 +241,14 @@ export class Nexus implements IContainer { } if (moduleConfig.services) { for (const serviceClass of moduleConfig.services) { - const serviceConfig = getMetadata( + const providerConfig = getMetadata( serviceClass, - METADATA_KEYS.SERVICE_METADATA + METADATA_KEYS.PROVIDER_METADATA ); - if (!serviceConfig) { + if (!providerConfig) { throw new InvalidService(serviceClass); } - this.set(serviceConfig.token as TokenType, { + this.set(providerConfig.token as TokenType, { useClass: serviceClass, }); } @@ -249,12 +256,12 @@ export class Nexus implements IContainer { if (moduleConfig.providers) { for (const provider of moduleConfig.providers) { if (isService(provider)) { - const serviceConfig = getMetadata( + const providerConfig = getMetadata( provider, - METADATA_KEYS.SERVICE_METADATA + METADATA_KEYS.PROVIDER_METADATA ); - if (serviceConfig) { - this.set(serviceConfig.token as TokenType, { + if (providerConfig) { + this.set(providerConfig.token as TokenType, { useClass: provider, }); } else { diff --git a/libs/core/src/decorators/index.ts b/libs/core/src/decorators/index.ts index 5e3db3f..5ca96d6 100644 --- a/libs/core/src/decorators/index.ts +++ b/libs/core/src/decorators/index.ts @@ -1,5 +1,4 @@ export * from './module'; export * from './service'; -export * from './provider'; export * from './inject'; export * from './optional'; diff --git a/libs/core/src/decorators/provider.test.ts b/libs/core/src/decorators/provider.test.ts deleted file mode 100644 index 7a6a5c9..0000000 --- a/libs/core/src/decorators/provider.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Provider } from './provider'; -import { Token } from '../token'; - -describe('@Provider', () => { - it('should add provider metadata', () => { - const PROVIDER_TOKEN = new Token('PROVIDER'); - @Provider(PROVIDER_TOKEN) - class TestProvider {} - expect(TestProvider).toBeDefined(); - }); -}); diff --git a/libs/core/src/decorators/provider.ts b/libs/core/src/decorators/provider.ts deleted file mode 100644 index 5193379..0000000 --- a/libs/core/src/decorators/provider.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { ServiceConfig, TokenType } from '../types'; -import { METADATA_KEYS } from '../constants'; -import { setMetadata } from '../helpers'; - -/** - * Decorator that marks a class as a provider with a custom token. - * - * Use this for advanced scenarios where you want to register a class under a specific token. - * - * #### Usage - * ```typescript - * import { Provider, Token } from '@nexusdi/core'; - * - * const MY_TOKEN = new Token('MyToken'); - * - * @Provider(MY_TOKEN) - * class MyProvider {} - * ``` - * - * @param token The custom token for the provider (class constructor, symbol, or Token) - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @see https://nexus.js.org/docs/modules/tokens - * @publicApi - */ -export function Provider(token: TokenType): ClassDecorator { - return (target) => { - const config: ServiceConfig = { - token, - }; - setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); - }; -} diff --git a/libs/core/src/decorators/service.test.ts b/libs/core/src/decorators/service.test.ts index bc77ccc..e3d9f9d 100644 --- a/libs/core/src/decorators/service.test.ts +++ b/libs/core/src/decorators/service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { Service } from './service'; +import { Provider, Service } from './service'; import { Token } from '../token'; import { getMetadata } from '../helpers'; import { METADATA_KEYS } from '../constants'; @@ -15,7 +15,7 @@ describe('@Service', () => { it('should add service metadata with class as token', () => { @Service() class TestService {} - const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); + const metadata = getMetadata(TestService, METADATA_KEYS.PROVIDER_METADATA); expect(metadata.token).toBe(TestService); }); @@ -23,7 +23,16 @@ describe('@Service', () => { const CUSTOM_TOKEN = new Token('CUSTOM_SERVICE'); @Service(CUSTOM_TOKEN) class TestService {} - const metadata = getMetadata(TestService, METADATA_KEYS.SERVICE_METADATA); + const metadata = getMetadata(TestService, METADATA_KEYS.PROVIDER_METADATA); expect(metadata.token).toBe(CUSTOM_TOKEN); }); }); + +describe('@Provider', () => { + it('should add provider metadata', () => { + const PROVIDER_TOKEN = new Token('PROVIDER'); + @Provider(PROVIDER_TOKEN) + class TestProvider {} + expect(TestProvider).toBeDefined(); + }); +}); diff --git a/libs/core/src/decorators/service.ts b/libs/core/src/decorators/service.ts index 6eceec8..337488e 100644 --- a/libs/core/src/decorators/service.ts +++ b/libs/core/src/decorators/service.ts @@ -1,48 +1,51 @@ -import type { TokenType, ServiceConfig } from '../types'; +import type { TokenType, ProviderConfig } from '../types'; import { METADATA_KEYS } from '../constants'; import { setMetadata } from '../helpers'; /** - * Decorator that marks a class as a service for dependency injection. - * - * Use this to register a class as a singleton or transient service in the DI container. - * - * #### Usage - * ```typescript - * import { Service } from '@nexusdi/core'; - * - * @Service() - * class LoggerService {} - * ``` - * - * @param token Optional custom token for the service (class constructor, symbol, or Token) - * - * @see https://nexus.js.org/docs/modules/providers-and-services - * @publicApi + * Shared decorator factory for providers and services. + * Allows for future extensibility (e.g., scopes, types). */ -export function Service(token?: TokenType): ClassDecorator { - return (target) => { - const config: ServiceConfig = { - token: token || (target as unknown as TokenType), - }; - setMetadata(target, METADATA_KEYS.SERVICE_METADATA, config); +function makeProviderDecorator( + defaults?: Partial +): (tokenOrConfig?: TokenType | ProviderConfig) => ClassDecorator { + return (tokenOrConfig?: TokenType | ProviderConfig) => (target) => { + let config: ProviderConfig = { ...defaults }; + if ( + typeof tokenOrConfig === 'object' && + tokenOrConfig !== null && + ('scope' in tokenOrConfig || + 'singleton' in tokenOrConfig || + 'type' in tokenOrConfig) + ) { + config = { ...config, ...tokenOrConfig }; + } else if (tokenOrConfig) { + config.token = tokenOrConfig as TokenType; + } + if (!config.token) config.token = target as any; + setMetadata(target, METADATA_KEYS.PROVIDER_METADATA, config); }; } /** - * Decorator that marks a class as injectable for dependency injection. + * Decorator that marks a class as a provider for dependency injection. * - * This is an alias for `@Service()` and is provided for compatibility and clarity. + * Use this for advanced scenarios where you want to register a class under a specific token or with custom config. * - * #### Usage - * ```typescript - * import { Injectable } from '@nexusdi/core'; + * @param tokenOrConfig The custom token or config for the provider (class constructor, symbol, Token, or config object) + * @publicApi + */ +export const Provider = makeProviderDecorator(); + +/** + * Decorator that marks a class as a service for dependency injection. * - * @Injectable() - * class MyService {} - * ``` + * This is a specialized provider with singleton semantics and a 'service' type. * - * @see https://nexus.js.org/docs/modules/providers-and-services + * @param tokenOrConfig Optional custom token or config for the service (class constructor, symbol, Token, or config object) * @publicApi */ -export const Injectable = Service; +export const Service = makeProviderDecorator({ + singleton: true, + type: 'service', +}); diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 3c06c78..ac07620 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -18,14 +18,7 @@ export { Token } from './token'; export type { TokenType } from './types'; // Decorators -export { - Module, - Service, - Provider, - Inject, - Injectable, - Optional, -} from './decorators'; +export { Module, Service, Provider, Inject, Optional } from './decorators'; export { ContainerException, @@ -43,7 +36,7 @@ export { DynamicModule } from './module'; export type { Provider as ProviderType, ModuleProvider, - ServiceConfig, + ProviderConfig, ModuleConfig, InjectionMetadata, } from './types'; diff --git a/libs/core/src/types.test.ts b/libs/core/src/types.test.ts index 22f3887..ed03b66 100644 --- a/libs/core/src/types.test.ts +++ b/libs/core/src/types.test.ts @@ -6,6 +6,7 @@ import { type ServiceConfig, type ModuleConfig, type InjectionMetadata, + type ProviderConfig, } from './types'; import { METADATA_KEYS } from './constants'; @@ -46,56 +47,35 @@ describe('Types', () => { }); }); - // Provider group: Ensures all provider registration patterns are supported - describe('Provider', () => { + // ProviderConfig group: Ensures provider configuration is flexible and robust + describe('ProviderConfig', () => { /** - * Test: Provider with useClass - * Validates: Structure and value of provider object - * Value: Ensures DI can instantiate services from classes + * Test: ProviderConfig with token + * Validates: Structure and values + * Value: Ensures DI can register providers with explicit tokens and singleton flag */ - it('should define a provider with useClass', () => { - class TestService {} - const provider: { token: TokenType; useClass: typeof TestService } = { - token: new Token('TEST_TOKEN'), - useClass: TestService, + it('should define provider config with token', () => { + const token = new Token('PROVIDER_TOKEN'); + const config: { token: TokenType; singleton: boolean } = { + token, + singleton: true, }; - expect(provider.token).toBeInstanceOf(Token); - expect(provider.useClass).toBe(TestService); + expect(config.token).toBe(token); + expect(config.singleton).toBe(true); }); /** - * Test: Provider with useValue - * Validates: Structure and value of provider object - * Value: Ensures DI can inject static values or configs + * Test: ProviderConfig without token + * Validates: Structure and default values + * Value: Allows for simple singleton/non-singleton provider registration */ - it('should define a provider with useValue', () => { - const provider: { token: TokenType; useValue: string } = { - token: new Token('VALUE_TOKEN'), - useValue: 'test value', + it('should define provider config without token', () => { + const config: ProviderConfig = { + singleton: false, }; - expect(provider.token).toBeInstanceOf(Token); - expect(provider.useValue).toBe('test value'); - }); - /** - * Test: Provider with useFactory - * Validates: Structure, factory function, and dependencies - * Value: Enables dynamic/async provider creation in DI - */ - it('should define a provider with useFactory', () => { - const factory = () => 'factory result'; - const provider: { - token: TokenType; - useFactory: () => string; - deps: TokenType[]; - } = { - token: new Token('FACTORY_TOKEN'), - useFactory: factory, - deps: [Symbol('DEP1'), Symbol('DEP2')], - }; - expect(provider.token).toBeInstanceOf(Token); - expect(provider.useFactory).toBe(factory); - expect(provider.deps.length).toBe(2); + expect(config.token).toBeUndefined(); + expect(config.singleton).toBe(false); }); }); @@ -265,17 +245,17 @@ describe('Types', () => { }); /** - * Test: Service configuration with TokenType + * Test: Provider configuration with TokenType * Validates: Structure and values - * Value: Ensures service configs can be registered with typed tokens + * Value: Ensures provider configs can be registered with typed tokens */ - it('should work with service configuration', () => { + it('should work with provider configuration', () => { class TestService {} const token = new Token( - 'SERVICE_TOKEN' + 'PROVIDER_TOKEN' ) as unknown as TokenType; - const config: ServiceConfig = { + const config: ProviderConfig = { token, singleton: true, }; diff --git a/libs/core/src/types.ts b/libs/core/src/types.ts index 96f1738..805815f 100644 --- a/libs/core/src/types.ts +++ b/libs/core/src/types.ts @@ -40,16 +40,17 @@ export type ModuleProvider = | Constructor; /** - * Configuration for a service. Used with @Service and @Provider decorators. + * Configuration for a provider. Used with @Service and @Provider decorators. * * @example - * import { ServiceConfig } from '@nexusdi/core'; - * const config: ServiceConfig = { scope: 'singleton' }; + * import { ProviderConfig } from '@nexusdi/core'; + * const config: ProviderConfig = { scope: 'singleton' }; * @see https://nexus.js.org/docs/modules/providers-and-services */ -export type ServiceConfig = { +export type ProviderConfig = { token?: TokenType; singleton?: boolean; + type?: 'service' | 'value' | 'factory' | string; }; /** @@ -77,8 +78,6 @@ export interface IContainer { has(token: TokenType): boolean; - resolve(token: TokenType): T; - set(token: TokenType, provider: Provider): void; set(token: TokenType, serviceClass: Constructor): void; set(moduleClass: Constructor): void; @@ -89,6 +88,39 @@ export interface IContainer { exports?: TokenType[]; }): void; set(tokenOrModuleOrConfig: any, providerOrNothing?: any): void; + + /** + * Instantiates a new instance of the given class, resolving and injecting all dependencies. + * + * - Unlike `get`, this does not require the class to be registered as a provider and always returns a new instance. + * - Useful for transient or ad-hoc objects that are not managed by the container's provider registry. + * - Throws if dependencies cannot be resolved. + * + * @param target The class constructor to instantiate. + * @returns A new instance of the class with dependencies injected. + */ + resolve(ctor: Constructor): T; + + /** + * Creates a new child container that inherits the parent container's providers. + * + * @returns A new container with the same providers as the parent. + */ + createChildContainer(): IContainer; + + /** + * Clears all providers and instances from the container. + * + * @see https://nexus.js.org/docs/container/nexus-class#clear + */ + clear(): void; + + /** + * Lists all providers and modules registered in the container. + * + * @returns An object containing the providers and modules registered in the container. + */ + list(): { providers: TokenType[]; modules: string[] }; } /** diff --git a/libs/core/vite.config.ts b/libs/core/vite.config.ts index 12ddfd2..3226682 100644 --- a/libs/core/vite.config.ts +++ b/libs/core/vite.config.ts @@ -17,6 +17,13 @@ export default defineConfig(() => ({ coverage: { reportsDirectory: './test-output/vitest/coverage', provider: 'v8' as const, + exclude: [ + '**/index.ts', + 'dist/**/*', + 'vite.config.ts', + 'eslint.config.mjs', + './src/types.ts', + ], }, }, })); From 2243edc8a2f3bd58f4d2d76b7adb53e85ec364a8 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 19:02:43 +0200 Subject: [PATCH 05/10] refactor: dry wins --- libs/core/src/container.test.ts | 12 +- libs/core/src/container.ts | 113 +++++++++++------- libs/core/src/decorators/index.ts | 2 +- .../{service.test.ts => provider.test.ts} | 2 +- .../decorators/{service.ts => provider.ts} | 8 +- libs/core/src/guards.test.ts | 18 +-- libs/core/src/guards.ts | 22 +++- libs/core/tsconfig.lib.json | 1 + tsconfig.base.json | 1 - 9 files changed, 113 insertions(+), 66 deletions(-) rename libs/core/src/decorators/{service.test.ts => provider.test.ts} (96%) rename libs/core/src/decorators/{service.ts => provider.ts} (83%) diff --git a/libs/core/src/container.test.ts b/libs/core/src/container.test.ts index 097d1e8..68a814f 100644 --- a/libs/core/src/container.test.ts +++ b/libs/core/src/container.test.ts @@ -2,7 +2,12 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { Nexus } from './container'; import { Inject, Service, Module, Provider } from './decorators'; import { Token } from './token'; -import { InvalidToken, InvalidService, NoProvider } from './exceptions'; +import { + InvalidToken, + InvalidService, + NoProvider, + InvalidProvider, +} from './exceptions'; describe('Nexus', () => { let nexus: Nexus; @@ -355,10 +360,7 @@ describe('Nexus', () => { */ it('should throw if provider is missing useClass, useValue, and useFactory', () => { class Invalid {} - nexus.set(Invalid, {} as any); - expect(() => nexus.get(Invalid)).toThrow( - /Invalid provider configuration/ - ); + expect(() => nexus.set(Invalid, {} as any)).toThrowError(InvalidProvider); }); /** diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index 653eb81..9098223 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -9,7 +9,7 @@ import type { } from './types'; import { METADATA_KEYS } from './constants'; import { getMetadata } from './helpers'; -import { isTokenType, isProvider, isService } from './guards'; +import { isTokenType, isProvider, isConstructor } from './guards'; import { InvalidToken, NoProvider, @@ -191,27 +191,74 @@ export class Nexus implements IContainer { } /** - * Internal: Register a provider (class, value, or factory) for a token. + * Normalize a class or provider object into a { token, provider } pair. + * Ensures all registrations are consistent and robust. */ - private setProvider( - token: TokenType, - providerOrClass: Provider | Constructor - ): void { - if (!isTokenType(token)) { - throw new InvalidToken(token); - } - let provider: Provider; - if (typeof providerOrClass === 'function') { + private normalizeProvider(input: Provider | Constructor): { + token: TokenType; + provider: Provider; + } { + if (isConstructor(input)) { + // Class registration const providerConfig = getMetadata( - providerOrClass, + input, METADATA_KEYS.PROVIDER_METADATA ); if (!providerConfig) { - throw new InvalidService(providerOrClass); + throw new InvalidService(input); + } + const token = providerConfig.token || (input as TokenType); + return { token, provider: { useClass: input } }; + } else if (isProvider(input)) { + // Provider object registration + if ('token' in input && input.token) { + return { token: input.token as TokenType, provider: input }; + } else { + throw new InvalidProvider('Provider object must have a token'); + } + } else { + throw new InvalidProvider('Invalid provider type'); + } + } + + /** + * Internal: Register a provider (class, value, or factory) for a token. + */ + private setProvider( + tokenOrClass: TokenType | Constructor, + providerOrNothing?: Provider + ): void { + let token: TokenType; + let provider: Provider; + if (providerOrNothing !== undefined) { + if (!isTokenType(tokenOrClass)) { + throw new InvalidToken(tokenOrClass); + } + if (isConstructor(providerOrNothing)) { + // (token, class) registration + const providerObj = { + token: tokenOrClass, + useClass: providerOrNothing, + }; + ({ token, provider } = this.normalizeProvider(providerObj)); + } else if ( + providerOrNothing && + (providerOrNothing.useClass || + providerOrNothing.useValue || + providerOrNothing.useFactory) + ) { + // (token, provider object) registration + const providerWithToken = Object.assign({}, providerOrNothing, { + token: tokenOrClass, + }); + ({ token, provider } = this.normalizeProvider(providerWithToken)); + } else { + throw new InvalidProvider( + 'Invalid provider type. Only class constructors, symbols, or Token instances are allowed as providers.' + ); } - provider = { useClass: providerOrClass }; } else { - provider = providerOrClass; + ({ token, provider } = this.normalizeProvider(tokenOrClass as any)); } this.providers.set(token, provider); this.instances.delete(token); @@ -241,37 +288,19 @@ export class Nexus implements IContainer { } if (moduleConfig.services) { for (const serviceClass of moduleConfig.services) { - const providerConfig = getMetadata( - serviceClass, - METADATA_KEYS.PROVIDER_METADATA - ); - if (!providerConfig) { - throw new InvalidService(serviceClass); - } - this.set(providerConfig.token as TokenType, { - useClass: serviceClass, - }); + const { token, provider } = this.normalizeProvider(serviceClass); + this.setProvider(token as TokenType, provider as Provider); } } if (moduleConfig.providers) { for (const provider of moduleConfig.providers) { - if (isService(provider)) { - const providerConfig = getMetadata( - provider, - METADATA_KEYS.PROVIDER_METADATA - ); - if (providerConfig) { - this.set(providerConfig.token as TokenType, { - useClass: provider, - }); - } else { - throw new InvalidService(provider); - } - } else if (isProvider(provider)) { - this.set(provider.token as TokenType, provider); - } else { - throw new InvalidProvider('Invalid provider type'); - } + const { token, provider: normProvider } = this.normalizeProvider( + provider as any + ); + this.setProvider( + token as TokenType, + normProvider as Provider + ); } } } diff --git a/libs/core/src/decorators/index.ts b/libs/core/src/decorators/index.ts index 5ca96d6..fc559ce 100644 --- a/libs/core/src/decorators/index.ts +++ b/libs/core/src/decorators/index.ts @@ -1,4 +1,4 @@ export * from './module'; -export * from './service'; +export * from './provider'; export * from './inject'; export * from './optional'; diff --git a/libs/core/src/decorators/service.test.ts b/libs/core/src/decorators/provider.test.ts similarity index 96% rename from libs/core/src/decorators/service.test.ts rename to libs/core/src/decorators/provider.test.ts index e3d9f9d..50c293c 100644 --- a/libs/core/src/decorators/service.test.ts +++ b/libs/core/src/decorators/provider.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { Provider, Service } from './service'; +import { Provider, Service } from './provider'; import { Token } from '../token'; import { getMetadata } from '../helpers'; import { METADATA_KEYS } from '../constants'; diff --git a/libs/core/src/decorators/service.ts b/libs/core/src/decorators/provider.ts similarity index 83% rename from libs/core/src/decorators/service.ts rename to libs/core/src/decorators/provider.ts index 337488e..9c32276 100644 --- a/libs/core/src/decorators/service.ts +++ b/libs/core/src/decorators/provider.ts @@ -8,8 +8,8 @@ import { setMetadata } from '../helpers'; */ function makeProviderDecorator( defaults?: Partial -): (tokenOrConfig?: TokenType | ProviderConfig) => ClassDecorator { - return (tokenOrConfig?: TokenType | ProviderConfig) => (target) => { +): (tokenOrConfig?: TokenType | ProviderConfig) => ClassDecorator { + return (tokenOrConfig?: TokenType | ProviderConfig) => (target) => { let config: ProviderConfig = { ...defaults }; if ( typeof tokenOrConfig === 'object' && @@ -20,9 +20,9 @@ function makeProviderDecorator( ) { config = { ...config, ...tokenOrConfig }; } else if (tokenOrConfig) { - config.token = tokenOrConfig as TokenType; + config.token = tokenOrConfig as TokenType; } - if (!config.token) config.token = target as any; + if (!config.token) config.token = target as unknown as TokenType; setMetadata(target, METADATA_KEYS.PROVIDER_METADATA, config); }; } diff --git a/libs/core/src/guards.test.ts b/libs/core/src/guards.test.ts index 01d3df2..27f6beb 100644 --- a/libs/core/src/guards.test.ts +++ b/libs/core/src/guards.test.ts @@ -8,6 +8,7 @@ import { } from './guards'; import { Token } from './token'; import type { IContainer } from './types'; +import { Service, Provider } from './decorators'; /** * Guards: Ensures all public guard functions work as expected for valid and invalid cases @@ -64,14 +65,17 @@ describe('Guards', () => { }); describe('isService', () => { - it('should return true for class constructors', () => { - class Test {} - expect(isService(Test)).toBe(true); + it('should return true for decorated service/provider classes', () => { + @Service() + class TestService {} + expect(isService(TestService)).toBe(true); + @Provider() + class TestProvider {} + expect(isService(TestProvider)).toBe(true); }); - it('should return false for objects, null, undefined', () => { - expect(isService({})).toBe(false); - expect(isService(null)).toBe(false); - expect(isService(undefined)).toBe(false); + it('should return false for undecorated class constructors', () => { + class NotAService {} + expect(isService(NotAService)).toBe(false); }); }); diff --git a/libs/core/src/guards.ts b/libs/core/src/guards.ts index 6f8ce50..0c6e4ba 100644 --- a/libs/core/src/guards.ts +++ b/libs/core/src/guards.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // Type guards and validators for NexusDI public API -import type { TokenType, Provider, IContainer } from './types'; +import type { TokenType, Provider, Constructor, IContainer } from './types'; import { Token } from './token'; +import { getMetadata } from './helpers'; +import { METADATA_KEYS } from './constants'; /** * Checks if a value is a Token instance. @@ -49,14 +51,24 @@ export function isFactory(obj: unknown): obj is { useFactory: () => unknown } { } /** - * Checks if a value is a service class (has a constructor, a non-empty name, and a prototype object). + * Returns true if the value is a class constructor (function with a prototype). */ -export function isService(obj: unknown): obj is new (...args: any[]) => any { +export function isConstructor(obj: unknown): obj is Constructor { return ( typeof obj === 'function' && - !!(obj as any).name && + !!obj && (obj as any).prototype && - typeof (obj as any).prototype === 'object' + (obj as any).prototype.constructor === obj + ); +} + +/** + * Returns true if the value is a decorated service/provider class. + */ +export function isService(value: unknown): value is Constructor { + return ( + isConstructor(value) && + !!getMetadata(value, METADATA_KEYS.PROVIDER_METADATA) ); } diff --git a/libs/core/tsconfig.lib.json b/libs/core/tsconfig.lib.json index 19ac478..bebaa04 100644 --- a/libs/core/tsconfig.lib.json +++ b/libs/core/tsconfig.lib.json @@ -6,6 +6,7 @@ "outDir": "dist", "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", "emitDeclarationOnly": false, + "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, "types": ["node"], "lib": ["es2022", "esnext.decorators"] diff --git a/tsconfig.base.json b/tsconfig.base.json index 54acfec..5d10310 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -11,7 +11,6 @@ "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true, "noImplicitOverride": true, "noImplicitReturns": true, "noUnusedLocals": true, From e1917d5bfde9372fb39cb8e3c9c65f0229f85fbb Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 19:19:51 +0200 Subject: [PATCH 06/10] fix: migrate to native decorator metadata --- examples/react-ssr/app/entry.server.tsx | 1 - examples/react-ssr/tsconfig.app.json | 1 - examples/react-ssr/tsconfig.json | 1 - examples/react-ssr/tsconfig.spec.json | 3 +-- package.json | 3 +-- 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/react-ssr/app/entry.server.tsx b/examples/react-ssr/app/entry.server.tsx index 71314bd..df897cd 100644 --- a/examples/react-ssr/app/entry.server.tsx +++ b/examples/react-ssr/app/entry.server.tsx @@ -1,4 +1,3 @@ -import 'reflect-metadata'; import { PassThrough } from 'node:stream'; import type { diff --git a/examples/react-ssr/tsconfig.app.json b/examples/react-ssr/tsconfig.app.json index 4c246b5..7628e40 100644 --- a/examples/react-ssr/tsconfig.app.json +++ b/examples/react-ssr/tsconfig.app.json @@ -6,7 +6,6 @@ "types": ["@react-router/node", "vite/client", "node"], "esModuleInterop": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true, "jsx": "react-jsx", "module": "es2022", "resolveJsonModule": true, diff --git a/examples/react-ssr/tsconfig.json b/examples/react-ssr/tsconfig.json index 6661ab3..ad398af 100644 --- a/examples/react-ssr/tsconfig.json +++ b/examples/react-ssr/tsconfig.json @@ -16,7 +16,6 @@ "compilerOptions": { "jsx": "react-jsx", "experimentalDecorators": true, - "emitDecoratorMetadata": true, "moduleResolution": "bundler", "types": ["@react-router/node"] } diff --git a/examples/react-ssr/tsconfig.spec.json b/examples/react-ssr/tsconfig.spec.json index ca63a3a..a1ae8db 100644 --- a/examples/react-ssr/tsconfig.spec.json +++ b/examples/react-ssr/tsconfig.spec.json @@ -13,8 +13,7 @@ ], "jsx": "react-jsx", "module": "es2022", - "experimentalDecorators": true, - "emitDecoratorMetadata": true + "experimentalDecorators": true }, "include": [ "vite.config.ts", diff --git a/package.json b/package.json index c5e8d27..0d1429f 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,7 @@ "prism-react-renderer": "^2.3.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-router": "^7.6.2", - "reflect-metadata": "^0.2.2" + "react-router": "^7.6.2" }, "devDependencies": { "@commitlint/config-conventional": "^19.8.1", From c6353986113fa2e24a21080415532fb7e020f9ad Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Fri, 27 Jun 2025 19:25:01 +0200 Subject: [PATCH 07/10] chore: update to latest react-router --- examples/react-ssr/package.json | 8 +- package-lock.json | 2141 ++++++++++++++++++++++--------- package.json | 8 +- 3 files changed, 1521 insertions(+), 636 deletions(-) diff --git a/examples/react-ssr/package.json b/examples/react-ssr/package.json index ff05ddb..82bd95d 100644 --- a/examples/react-ssr/package.json +++ b/examples/react-ssr/package.json @@ -5,15 +5,15 @@ "scripts": {}, "sideEffects": false, "dependencies": { - "@react-router/node": "^7.6.2", - "@react-router/serve": "^7.6.2", + "@react-router/node": "^7.6.3", + "@react-router/serve": "^7.6.3", "isbot": "^4.4.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-router": "^7.6.2" + "react-router": "^7.6.3" }, "devDependencies": { - "@react-router/dev": "^7.6.2", + "@react-router/dev": "^7.6.3", "@types/node": "^22", "@types/react": "19.1.8", "@types/react-dom": "19.1.6" diff --git a/package-lock.json b/package-lock.json index cb56981..7aa246f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,8 @@ ], "dependencies": { "@mdx-js/react": "^3.0.0", - "@react-router/node": "^7.6.2", - "@react-router/serve": "^7.6.2", + "@react-router/node": "^7.6.3", + "@react-router/serve": "^7.6.3", "@tailwindcss/vite": "^4.1.10", "@testing-library/user-event": "^14.6.1", "class-variance-authority": "^0.7.1", @@ -29,8 +29,7 @@ "prism-react-renderer": "^2.3.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-router": "^7.6.2", - "reflect-metadata": "^0.2.2" + "react-router": "^7.6.3" }, "devDependencies": { "@commitlint/config-conventional": "^19.8.1", @@ -46,7 +45,7 @@ "@nx/vite": "21.2.1", "@nx/web": "21.2.1", "@playwright/test": "^1.53.1", - "@react-router/dev": "^7.6.2", + "@react-router/dev": "^7.6.3", "@swc-node/register": "~1.9.1", "@swc/cli": "~0.6.0", "@swc/core": "~1.5.7", @@ -91,15 +90,15 @@ "examples/react-ssr": { "name": "@nexusdi/react-ssr", "dependencies": { - "@react-router/node": "^7.6.2", - "@react-router/serve": "^7.6.2", + "@react-router/node": "^7.6.3", + "@react-router/serve": "^7.6.3", "isbot": "^4.4.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-router": "^7.6.2" + "react-router": "^7.6.3" }, "devDependencies": { - "@react-router/dev": "^7.6.2", + "@react-router/dev": "^7.6.3", "@types/node": "^22", "@types/react": "19.1.8", "@types/react-dom": "19.1.6" @@ -109,32 +108,14 @@ "name": "@nexusdi/react-router-e2e", "version": "0.0.1" }, - "libs": { - "name": "@nexusdi/typeorm", - "version": "0.0.1", - "dependencies": { - "tslib": "^2.3.0" - } - }, "libs/core": { "name": "@nexusdi/core", "version": "0.2.1", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.6.0" }, "devDependencies": { - "tslib": "^2.8.1" - }, - "peerDependencies": { - "reflect-metadata": "^0.2.2" - } - }, - "libs/typeorm": { - "name": "@nexusdi/typeorm", - "version": "0.0.1", - "extraneous": true, - "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.6.0" } }, "node_modules/@ampproject/remapping": { @@ -165,9 +146,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "dev": true, "license": "MIT", "engines": { @@ -175,22 +156,22 @@ } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.27.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -333,22 +314,43 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", @@ -527,13 +529,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1064,17 +1066,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz", + "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.27.7", "globals": "^11.1.0" }, "engines": { @@ -1112,13 +1114,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz", + "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -1460,16 +1463,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz", + "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/plugin-transform-destructuring": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.27.7" }, "engines": { "node": ">=6.9.0" @@ -1529,9 +1533,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "license": "MIT", "dependencies": { @@ -2135,17 +2139,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2164,9 +2168,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, "license": "MIT", "dependencies": { @@ -2206,13 +2210,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/cli/node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "dev": true, - "license": "MIT" - }, "node_modules/@commitlint/config-conventional": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", @@ -2283,19 +2280,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/format/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/is-ignored": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", @@ -2348,19 +2332,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/load/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -2461,12 +2432,15 @@ "node": ">=v18" } }, - "node_modules/@commitlint/read/node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "node_modules/@commitlint/read/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/@commitlint/resolve-extends": { "version": "19.8.1", @@ -2628,19 +2602,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@cypress/request": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz", @@ -2704,7 +2665,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@emnapi/wasi-threads": "1.0.2", @@ -2715,7 +2676,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -2725,7 +2686,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -3599,6 +3560,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -3720,6 +3714,39 @@ } } }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -3807,6 +3834,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -3825,6 +3885,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -4619,6 +4712,22 @@ "resolve": "1.22.8" } }, + "node_modules/@module-federation/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@module-federation/cli/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -4775,12 +4884,28 @@ } } }, - "node_modules/@module-federation/dts-plugin/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "license": "MIT", + "node_modules/@module-federation/dts-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4869,6 +4994,22 @@ "find-pkg": "2.0.0" } }, + "node_modules/@module-federation/manifest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@module-federation/manifest/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -5173,6 +5314,22 @@ } } }, + "node_modules/@module-federation/node/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@module-federation/node/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -5701,14 +5858,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", - "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", - "dev": true, + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", "license": "MIT", + "optional": true, "dependencies": { - "@emnapi/core": "^1.1.0", - "@emnapi/runtime": "^1.1.0", + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, @@ -5871,6 +6028,19 @@ "@nx/devkit": "19.5.3" } }, + "node_modules/@nrwl/devkit/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/@nrwl/devkit/node_modules/@nx/devkit": { "version": "19.5.3", "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.5.3.tgz", @@ -6072,6 +6242,41 @@ "node": ">= 10" } }, + "node_modules/@nrwl/devkit/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nrwl/devkit/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@nrwl/devkit/node_modules/nx": { "version": "20.8.2", "resolved": "https://registry.npmjs.org/nx/-/nx-20.8.2.tgz", @@ -6234,6 +6439,39 @@ } } }, + "node_modules/@nx/eslint-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@nx/express": { "version": "21.2.1", "resolved": "https://registry.npmjs.org/@nx/express/-/express-21.2.1.tgz", @@ -6325,6 +6563,39 @@ } } }, + "node_modules/@nx/js/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nx/js/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@nx/module-federation": { "version": "21.2.1", "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-21.2.1.tgz", @@ -6616,6 +6887,39 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@nx/workspace/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nx/workspace/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@phenomnomnominal/tsquery": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", @@ -6664,9 +6968,9 @@ "license": "MIT" }, "node_modules/@react-router/dev": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.2.tgz", - "integrity": "sha512-BuG83Ug2C/P+zMYErTz/KKuXoxbOefh3oR66r13XWG9txwooC9nt2QDt2u8yt7Eo/9BATnx+TmXnOHEWqMyB8w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.3.tgz", + "integrity": "sha512-nnJQMVeE+LDViFTQDxeQV5FcfJ48a6aCScrFHwPHWgViQmiJxUBtDU1Pl7XZKEoTus5KDg/W3Vz2spiY6wXg3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6679,14 +6983,13 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.22.5", "@npmcli/package-json": "^4.0.1", - "@react-router/node": "7.6.2", + "@react-router/node": "7.6.3", "arg": "^5.0.1", "babel-dead-code-elimination": "^1.0.6", "chokidar": "^4.0.0", "dedent": "^1.5.3", "es-module-lexer": "^1.3.1", "exit-hook": "2.2.1", - "fs-extra": "^10.0.0", "jsesc": "3.0.2", "lodash": "^4.17.21", "pathe": "^1.1.2", @@ -6695,6 +6998,7 @@ "react-refresh": "^0.14.0", "semver": "^7.3.7", "set-cookie-parser": "^2.6.0", + "tinyglobby": "^0.2.14", "valibot": "^0.41.0", "vite-node": "^3.1.4" }, @@ -6705,10 +7009,10 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@react-router/serve": "^7.6.2", - "react-router": "^7.6.2", + "@react-router/serve": "^7.6.3", + "react-router": "^7.6.3", "typescript": "^5.1.0", - "vite": "^5.1.0 || ^6.0.0", + "vite": "^5.1.0 || ^6.0.0 || ^7.0.0", "wrangler": "^3.28.2 || ^4.0.0" }, "peerDependenciesMeta": { @@ -6723,21 +7027,6 @@ } } }, - "node_modules/@react-router/dev/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@react-router/dev/node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -6752,19 +7041,19 @@ } }, "node_modules/@react-router/express": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.6.2.tgz", - "integrity": "sha512-b1XwP2ZknWG6yNl1aEAJ+yx0Alk85+iLk5y521MOhh2lCKPNyFOuX4Gw8hI3E4IXgDEPqiZ+lipmrIb7XkLNZQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.6.3.tgz", + "integrity": "sha512-45wLv2pNVDfnd4mZXYaxbqGE2wOzisQQAXSCHrWhkUn9CvJkaqC9cx82rzfB1UnGvyeupZxGgLxaG0b38pTEOA==", "license": "MIT", "dependencies": { - "@react-router/node": "7.6.2" + "@react-router/node": "7.6.3" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { "express": "^4.17.1 || ^5", - "react-router": "7.6.2", + "react-router": "7.6.3", "typescript": "^5.1.0" }, "peerDependenciesMeta": { @@ -6774,21 +7063,18 @@ } }, "node_modules/@react-router/node": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.6.2.tgz", - "integrity": "sha512-KrxfnfJVU1b+020VKemkxpc7ssItsAL8MOJthcoGwPyKwrgovdwc+8NKJUqw3P7yk/Si0ZmVh9QYAzi9qF96dg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.6.3.tgz", + "integrity": "sha512-CgqYAGjrfW/Al0LbWhQ60joDci5/H3ix4IU5UwlKLtqmNPzuSUTBkCrxit3jHuMYqaBaGfyRpT7kIeb1YZ4nqA==", "license": "MIT", "dependencies": { - "@mjackson/node-fetch-server": "^0.2.0", - "source-map-support": "^0.5.21", - "stream-slice": "^0.1.2", - "undici": "^6.19.2" + "@mjackson/node-fetch-server": "^0.2.0" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { - "react-router": "7.6.2", + "react-router": "7.6.3", "typescript": "^5.1.0" }, "peerDependenciesMeta": { @@ -6797,33 +7083,14 @@ } } }, - "node_modules/@react-router/node/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==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-router/node/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/@react-router/serve": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.6.2.tgz", - "integrity": "sha512-VTdvB8kdZEtYeQML9TFJiIZnPefv94LfmLx5qQ0SJSesel/hQolnfpWEkLJ9WtBO+/10CulAvg6y5UwiceUFTQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.6.3.tgz", + "integrity": "sha512-SbIXApeaNPM9rCkrcFU+VXYXCIh332cT2L0ikykWBCe6O9ZGE6Y1q5ZQ8puEf5Pmqwm77kIRoY9rACUbnXzfxA==", "license": "MIT", "dependencies": { - "@react-router/express": "7.6.2", - "@react-router/node": "7.6.2", + "@react-router/express": "7.6.3", + "@react-router/node": "7.6.3", "compression": "^1.7.4", "express": "^4.19.2", "get-port": "5.1.1", @@ -6837,7 +7104,7 @@ "node": ">=20.0.0" }, "peerDependencies": { - "react-router": "7.6.2" + "react-router": "7.6.3" } }, "node_modules/@react-router/serve/node_modules/source-map": { @@ -6897,9 +7164,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz", - "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", "cpu": [ "arm" ], @@ -6910,9 +7177,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz", - "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", "cpu": [ "arm64" ], @@ -6923,9 +7190,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz", - "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", "cpu": [ "arm64" ], @@ -6936,9 +7203,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz", - "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", "cpu": [ "x64" ], @@ -6949,9 +7216,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz", - "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", "cpu": [ "arm64" ], @@ -6962,9 +7229,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz", - "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", "cpu": [ "x64" ], @@ -6975,9 +7242,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz", - "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", "cpu": [ "arm" ], @@ -6988,9 +7255,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz", - "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", "cpu": [ "arm" ], @@ -7001,9 +7268,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz", - "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", "cpu": [ "arm64" ], @@ -7014,9 +7281,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz", - "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", "cpu": [ "arm64" ], @@ -7027,9 +7294,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz", - "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", "cpu": [ "loong64" ], @@ -7040,9 +7307,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz", - "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", "cpu": [ "ppc64" ], @@ -7053,9 +7320,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz", - "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", "cpu": [ "riscv64" ], @@ -7066,9 +7333,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz", - "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", "cpu": [ "riscv64" ], @@ -7079,9 +7346,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz", - "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", "cpu": [ "s390x" ], @@ -7092,9 +7359,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz", - "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", "cpu": [ "x64" ], @@ -7105,9 +7372,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz", - "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", "cpu": [ "x64" ], @@ -7118,9 +7385,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz", - "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", "cpu": [ "arm64" ], @@ -7131,9 +7398,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz", - "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", "cpu": [ "ia32" ], @@ -7144,9 +7411,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz", - "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", "cpu": [ "x64" ], @@ -7157,27 +7424,28 @@ ] }, "node_modules/@rspack/binding": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.3.15.tgz", - "integrity": "sha512-utNPuJglLO5lW9XbwIqjB7+2ilMo6JkuVLTVdnNVKU94FW7asn9F/qV+d+MgjUVqU1QPCGm0NuGO9xhbgeJ7pg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.4.1.tgz", + "integrity": "sha512-zYgOmI+LC2zxB/LIcnaeK66ElFHaPChdWzRruTT1LAFFwpgGkBGAwFoP27PDnxQW0Aejci21Ld8X9tyxm08QFw==", "dev": true, "license": "MIT", "optionalDependencies": { - "@rspack/binding-darwin-arm64": "1.3.15", - "@rspack/binding-darwin-x64": "1.3.15", - "@rspack/binding-linux-arm64-gnu": "1.3.15", - "@rspack/binding-linux-arm64-musl": "1.3.15", - "@rspack/binding-linux-x64-gnu": "1.3.15", - "@rspack/binding-linux-x64-musl": "1.3.15", - "@rspack/binding-win32-arm64-msvc": "1.3.15", - "@rspack/binding-win32-ia32-msvc": "1.3.15", - "@rspack/binding-win32-x64-msvc": "1.3.15" + "@rspack/binding-darwin-arm64": "1.4.1", + "@rspack/binding-darwin-x64": "1.4.1", + "@rspack/binding-linux-arm64-gnu": "1.4.1", + "@rspack/binding-linux-arm64-musl": "1.4.1", + "@rspack/binding-linux-x64-gnu": "1.4.1", + "@rspack/binding-linux-x64-musl": "1.4.1", + "@rspack/binding-wasm32-wasi": "1.4.1", + "@rspack/binding-win32-arm64-msvc": "1.4.1", + "@rspack/binding-win32-ia32-msvc": "1.4.1", + "@rspack/binding-win32-x64-msvc": "1.4.1" } }, "node_modules/@rspack/binding-darwin-arm64": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.15.tgz", - "integrity": "sha512-f+DnVRENRdVe+ufpZeqTtWAUDSTnP48jVo7x9KWsXf8XyJHUi+eHKEPrFoy1HvL1/k5yJ3HVnFBh1Hb9cNIwSg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-enh5DYbpaexdEmjbcxj3BJDauP3w+20jFKWvKROtAQV350PUw0bf2b4WOgngIH9hBzlfjpXNYAk6T5AhVAlY3Q==", "cpu": [ "arm64" ], @@ -7189,9 +7457,9 @@ ] }, "node_modules/@rspack/binding-darwin-x64": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.15.tgz", - "integrity": "sha512-TfUvEIBqYUT2OK01BYXb2MNcZeZIhAnJy/5aj0qV0uy4KlvwW63HYcKWa1sFd4Ac7bnGShDkanvP3YEuHOFOyg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.4.1.tgz", + "integrity": "sha512-KoehyhBji4TLXhn4mqOUw6xsQNRzNVA9XcCm1Jx+M1Qb0dhMTNfduvBSyXuRV5+/QaRbk7+4UJbyRNFUtt96kA==", "cpu": [ "x64" ], @@ -7203,9 +7471,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.15.tgz", - "integrity": "sha512-D/YjYk9snKvYm1Elotq8/GsEipB4ZJWVv/V8cZ+ohhFNOPzygENi6JfyI06TryBTQiN0/JDZqt/S9RaWBWnMqw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.4.1.tgz", + "integrity": "sha512-PJ5cHqvrj1bK7jH5DVrdKoR8Fy+p6l9baxXajq/6xWTxP+4YTdEtLsRZnpLMS1Ho2RRpkxDWJn+gdlKuleNioQ==", "cpu": [ "arm64" ], @@ -7217,9 +7485,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.15.tgz", - "integrity": "sha512-lJbBsPMOiR0hYPCSM42yp7QiZjfo0ALtX7ws2wURpsQp3BMfRVAmXU3Ixpo2XCRtG1zj8crHaCmAWOJTS0smsA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.4.1.tgz", + "integrity": "sha512-cpDz+z3FwVQfK6VYfXQEb0ym6fFIVmvK4y3R/2VAbVGWYVxZB5I6AcSdOWdDnpppHmcHpf+qQFlwhHvbpMMJNQ==", "cpu": [ "arm64" ], @@ -7231,9 +7499,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.15.tgz", - "integrity": "sha512-qGB8ucHklrzNg6lsAS36VrBsCbOw0acgpQNqTE5cuHWrp1Pu3GFTRiFEogenxEmzoRbohMZt0Ev5grivrcgKBQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.4.1.tgz", + "integrity": "sha512-jjTx53CpiYWK7fAv5qS8xHEytFK6gLfZRk+0kt2YII6uqez/xQ3SRcboreH8XbJcBoxINBzMNMf5/SeMBZ939A==", "cpu": [ "x64" ], @@ -7245,9 +7513,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.15.tgz", - "integrity": "sha512-qRn6e40fLQP+N2rQD8GAj/h4DakeTIho32VxTIaHRVuzw68ZD7VmKkwn55ssN370ejmey35ZdoNFNE12RBrMZA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.4.1.tgz", + "integrity": "sha512-FAyR3Og81Smtr/CnsuTiW4ZCYAPCqeV73lzMKZ9xdVUgM9324ryEgqgX38GZLB5Mo7cvQhv7/fpMeHQo16XQCw==", "cpu": [ "x64" ], @@ -7258,10 +7526,24 @@ "linux" ] }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.4.1.tgz", + "integrity": "sha512-3Q1VICIQP4GsaTJEmmwfowQ48NvhlL0CKH88l5+mbji2rBkGx7yR67pPdfCVNjXcCtFoemTYw98eaumJTjC++g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + } + }, "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.15.tgz", - "integrity": "sha512-7uJ7dWhO1nWXJiCss6Rslz8hoAxAhFpwpbWja3eHgRb7O4NPHg6MWw63AQSI2aFVakreenfu9yXQqYfpVWJ2dA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.4.1.tgz", + "integrity": "sha512-DdLPOy1J98kn45uEhiEqlBKgMvet+AxOzX2OcrnU0wQXthGM9gty1YXYNryOhlK+X+eOcwcP3GbnDOAKi8nKqw==", "cpu": [ "arm64" ], @@ -7273,9 +7555,9 @@ ] }, "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.15.tgz", - "integrity": "sha512-UsaWTYCjDiSCB0A0qETgZk4QvhwfG8gCrO4SJvA+QSEWOmgSai1YV70prFtLLIiyT9mDt1eU3tPWl1UWPRU/EQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.4.1.tgz", + "integrity": "sha512-13s8fYtyC9DyvKosD2Kvzd6fVZDZZyPp91L4TEXWaO0CFhaCbtLTYIntExq9MwtKHYKKx7bchIFw93o0xjKjUg==", "cpu": [ "ia32" ], @@ -7287,9 +7569,9 @@ ] }, "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.15.tgz", - "integrity": "sha512-ZnDIc9Es8EF94MirPDN+hOMt7tkb8nMEbRJFKLMmNd0ElNPgsql+1cY5SqyGRH1hsKB87KfSUQlhFiKZvzbfIg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.4.1.tgz", + "integrity": "sha512-ubQW8FcLnwljDanwTzkC9Abyo59gmX8m9uVr1GHOEuEU9Cua0KMijX2j/MYfiziz4nuQgv1saobY7K1I5nE3YA==", "cpu": [ "x64" ], @@ -7301,14 +7583,14 @@ ] }, "node_modules/@rspack/core": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.3.15.tgz", - "integrity": "sha512-QuElIC8jXSKWAp0LSx18pmbhA7NiA5HGoVYesmai90UVxz98tud0KpMxTVCg+0lrLrnKZfCWN9kwjCxM5pGnrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.4.1.tgz", + "integrity": "sha512-UTRCTQk2G8YiPBiMvfn8FcysxeO4Muek6a/Z39Cw2r4ZI8k5iPnKiyZboTJLS7120PwWBw2SO+QQje35Z44x0g==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime-tools": "0.14.3", - "@rspack/binding": "1.3.15", + "@module-federation/runtime-tools": "0.15.0", + "@rspack/binding": "1.4.1", "@rspack/lite-tapable": "1.0.1" }, "engines": { @@ -7324,62 +7606,62 @@ } }, "node_modules/@rspack/core/node_modules/@module-federation/error-codes": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.14.3.tgz", - "integrity": "sha512-sBJ3XKU9g5Up31jFeXPFsD8AgORV7TLO/cCSMuRewSfgYbG/3vSKLJmfHrO6+PvjZSb9VyV2UaF02ojktW65vw==", - "dev": true, + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.15.0.tgz", + "integrity": "sha512-CFJSF+XKwTcy0PFZ2l/fSUpR4z247+Uwzp1sXVkdIfJ/ATsnqf0Q01f51qqSEA6MYdQi6FKos9FIcu3dCpQNdg==", + "dev": true, "license": "MIT" }, "node_modules/@rspack/core/node_modules/@module-federation/runtime": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.14.3.tgz", - "integrity": "sha512-7ZHpa3teUDVhraYdxQGkfGHzPbjna4LtwbpudgzAxSLLFxLDNanaxCuSeIgSM9c+8sVUNC9kvzUgJEZB0krPJw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.15.0.tgz", + "integrity": "sha512-dTPsCNum9Bhu3yPOcrPYq0YnM9eCMMMNB1wuiqf1+sFbQlNApF0vfZxooqz3ln0/MpgE0jerVvFsLVGfqvC9Ug==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.14.3", - "@module-federation/runtime-core": "0.14.3", - "@module-federation/sdk": "0.14.3" + "@module-federation/error-codes": "0.15.0", + "@module-federation/runtime-core": "0.15.0", + "@module-federation/sdk": "0.15.0" } }, "node_modules/@rspack/core/node_modules/@module-federation/runtime-core": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.14.3.tgz", - "integrity": "sha512-xMFQXflLVW/AJTWb4soAFP+LB4XuhE7ryiLIX8oTyUoBBgV6U2OPghnFljPjeXbud72O08NYlQ1qsHw1kN/V8Q==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.15.0.tgz", + "integrity": "sha512-RYzI61fRDrhyhaEOXH3AgIGlHiot0wPFXu7F43cr+ZnTi+VlSYWLdlZ4NBuT9uV6JSmH54/c+tEZm5SXgKR2sQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "0.14.3", - "@module-federation/sdk": "0.14.3" + "@module-federation/error-codes": "0.15.0", + "@module-federation/sdk": "0.15.0" } }, "node_modules/@rspack/core/node_modules/@module-federation/runtime-tools": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.14.3.tgz", - "integrity": "sha512-QBETX7iMYXdSa3JtqFlYU+YkpymxETZqyIIRiqg0gW+XGpH3jgU68yjrme2NBJp7URQi/CFZG8KWtfClk0Pjgw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.15.0.tgz", + "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.14.3", - "@module-federation/webpack-bundler-runtime": "0.14.3" + "@module-federation/runtime": "0.15.0", + "@module-federation/webpack-bundler-runtime": "0.15.0" } }, "node_modules/@rspack/core/node_modules/@module-federation/sdk": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.14.3.tgz", - "integrity": "sha512-THJZMfbXpqjQOLblCQ8jjcBFFXsGRJwUWE9l/Q4SmuCSKMgAwie7yLT0qSGrHmyBYrsUjAuy+xNB4nfKP0pnGw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.15.0.tgz", + "integrity": "sha512-PWiYbGcJrKUD6JZiEPihrXhV3bgXdll4bV7rU+opV7tHaun+Z0CdcawjZ82Xnpb8MCPGmqHwa1MPFeUs66zksw==", "dev": true, "license": "MIT" }, "node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.14.3.tgz", - "integrity": "sha512-hIyJFu34P7bY2NeMIUHAS/mYUHEY71VTAsN0A0AqEJFSVPszheopu9VdXq0VDLrP9KQfuXT8SDxeYeJXyj0mgA==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.15.0.tgz", + "integrity": "sha512-i+3wu2Ljh2TmuUpsnjwZVupOVqV50jP0ndA8PSP4gwMKlgdGeaZ4VH5KkHAXGr2eiYUxYLMrJXz1+eILJqeGDg==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.14.3", - "@module-federation/sdk": "0.14.3" + "@module-federation/runtime": "0.15.0", + "@module-federation/sdk": "0.15.0" } }, "node_modules/@rspack/lite-tapable": { @@ -8237,9 +8519,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz", - "integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -8248,13 +8530,13 @@ "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.10" + "tailwindcss": "4.1.11" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz", - "integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -8265,24 +8547,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.10", - "@tailwindcss/oxide-darwin-arm64": "4.1.10", - "@tailwindcss/oxide-darwin-x64": "4.1.10", - "@tailwindcss/oxide-freebsd-x64": "4.1.10", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.10", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.10", - "@tailwindcss/oxide-linux-x64-musl": "4.1.10", - "@tailwindcss/oxide-wasm32-wasi": "4.1.10", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.10" + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz", - "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", "cpu": [ "arm64" ], @@ -8296,9 +8578,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz", - "integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", "cpu": [ "arm64" ], @@ -8312,9 +8594,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz", - "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", "cpu": [ "x64" ], @@ -8328,9 +8610,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz", - "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", "cpu": [ "x64" ], @@ -8344,9 +8626,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz", - "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", "cpu": [ "arm" ], @@ -8360,9 +8642,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz", - "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", "cpu": [ "arm64" ], @@ -8376,9 +8658,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz", - "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", "cpu": [ "arm64" ], @@ -8392,9 +8674,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz", - "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", "cpu": [ "x64" ], @@ -8408,9 +8690,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz", - "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", "cpu": [ "x64" ], @@ -8424,9 +8706,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz", - "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -8444,7 +8726,7 @@ "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.10", + "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, @@ -8452,64 +8734,10 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.4.3", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.4.3", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.10", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.0", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz", - "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", "cpu": [ "arm64" ], @@ -8523,9 +8751,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz", - "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", "cpu": [ "x64" ], @@ -8539,17 +8767,17 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.10.tgz", - "integrity": "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.10", - "@tailwindcss/oxide": "4.1.10", - "tailwindcss": "4.1.10" + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" }, "peerDependencies": { - "vite": "^5.2.0 || ^6" + "vite": "^5.2.0 || ^6 || ^7" } }, "node_modules/@testing-library/dom": { @@ -8571,6 +8799,37 @@ "node": ">=18" } }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@testing-library/react": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", @@ -8643,7 +8902,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -10592,15 +10851,12 @@ } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -11105,6 +11361,39 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/babel-plugin-const-enum": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz", @@ -11263,14 +11552,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -11302,13 +11591,13 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -11880,16 +12169,13 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -12196,16 +12482,6 @@ "dev": true, "license": "MIT" }, - "node_modules/commitizen/node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/commitlint": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-19.8.1.tgz", @@ -13428,9 +13704,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.174", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.174.tgz", - "integrity": "sha512-HE43yYdUUiJVjewV2A9EP8o89Kb4AqMKplMQP2IxEPUws1Etu/ZkdsgUDabUZ/WmbP4ZbvJDOcunvbBUPPIfmw==", + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "dev": true, "license": "ISC" }, @@ -14327,6 +14603,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -14338,6 +14630,23 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -15714,16 +16023,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -15756,6 +16055,13 @@ "node": ">=0.10.0" } }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/global-prefix/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -16214,6 +16520,39 @@ "node": ">=12" } }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/http-signature": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", @@ -16424,11 +16763,14 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/inquirer": { "version": "8.2.5", @@ -16457,8 +16799,41 @@ "node": ">=12.0.0" } }, - "node_modules/inquirer/node_modules/ora": { - "version": "5.4.1", + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, @@ -17302,6 +17677,22 @@ "node": ">=10" } }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jake/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -17313,6 +17704,23 @@ "concat-map": "0.0.1" } }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jake/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -17358,15 +17766,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17439,15 +17867,35 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17490,15 +17938,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17555,15 +18023,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17658,19 +18146,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-leak-detector/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -17709,15 +18184,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17765,15 +18260,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -17864,6 +18379,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -17897,6 +18445,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runner/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17952,6 +18533,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -17984,15 +18598,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } @@ -18037,6 +18671,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-util/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -18068,14 +18735,34 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -18123,6 +18810,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -19139,6 +19859,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -19498,9 +20251,9 @@ } }, "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true, "license": "MIT", "funding": { @@ -20051,6 +20804,19 @@ "tslib": "^2.3.0" } }, + "node_modules/nx-github-pages/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/nx-github-pages/node_modules/@nx/devkit": { "version": "19.5.3", "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.5.3.tgz", @@ -20265,6 +21031,41 @@ "node": ">= 10" } }, + "node_modules/nx-github-pages/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nx-github-pages/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/nx-github-pages/node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -20384,6 +21185,51 @@ "node": ">=6" } }, + "node_modules/nx/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/nx/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nx/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/nx/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -20655,6 +21501,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -21163,14 +22042,14 @@ } }, "node_modules/pkg-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", - "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.1.tgz", + "integrity": "sha512-eY0QFb6eSwc9+0d/5D2lFFUq+A3n3QNGSy/X2Nvp+6MfzGw2u6EbA7S80actgjY1lkvvI0pqB+a4hioMh443Ew==", "dev": true, "license": "MIT", "dependencies": { - "confbox": "^0.2.1", - "exsolve": "^1.0.1", + "confbox": "^0.2.2", + "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, @@ -21337,18 +22216,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prism-react-renderer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", @@ -21690,9 +22557,9 @@ } }, "node_modules/react-router": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz", - "integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz", + "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -21759,12 +22626,6 @@ "node": ">= 12.13.0" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -22025,9 +22886,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", - "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -22040,26 +22901,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.0", - "@rollup/rollup-android-arm64": "4.44.0", - "@rollup/rollup-darwin-arm64": "4.44.0", - "@rollup/rollup-darwin-x64": "4.44.0", - "@rollup/rollup-freebsd-arm64": "4.44.0", - "@rollup/rollup-freebsd-x64": "4.44.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", - "@rollup/rollup-linux-arm-musleabihf": "4.44.0", - "@rollup/rollup-linux-arm64-gnu": "4.44.0", - "@rollup/rollup-linux-arm64-musl": "4.44.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-musl": "4.44.0", - "@rollup/rollup-linux-s390x-gnu": "4.44.0", - "@rollup/rollup-linux-x64-gnu": "4.44.0", - "@rollup/rollup-linux-x64-musl": "4.44.0", - "@rollup/rollup-win32-arm64-msvc": "4.44.0", - "@rollup/rollup-win32-ia32-msvc": "4.44.0", - "@rollup/rollup-win32-x64-msvc": "4.44.0", + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", "fsevents": "~2.3.2" } }, @@ -22881,12 +23742,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stream-slice": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", - "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", - "license": "MIT" - }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -23328,9 +24183,9 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", - "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "license": "MIT" }, "node_modules/tapable": { @@ -23718,9 +24573,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", "dev": true, "license": "MIT" }, @@ -23948,7 +24803,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/tsscmp": { @@ -24219,15 +25074,6 @@ "through": "^2.3.8" } }, - "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -24871,6 +25717,13 @@ "dev": true, "license": "MIT" }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -25288,6 +26141,38 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 0d1429f..b06f7c3 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "private": true, "dependencies": { "@mdx-js/react": "^3.0.0", - "@react-router/node": "^7.6.2", - "@react-router/serve": "^7.6.2", + "@react-router/node": "^7.6.3", + "@react-router/serve": "^7.6.3", "@tailwindcss/vite": "^4.1.10", "@testing-library/user-event": "^14.6.1", "class-variance-authority": "^0.7.1", @@ -27,7 +27,7 @@ "prism-react-renderer": "^2.3.0", "react": "19.1.0", "react-dom": "19.1.0", - "react-router": "^7.6.2" + "react-router": "^7.6.3" }, "devDependencies": { "@commitlint/config-conventional": "^19.8.1", @@ -43,7 +43,7 @@ "@nx/vite": "21.2.1", "@nx/web": "21.2.1", "@playwright/test": "^1.53.1", - "@react-router/dev": "^7.6.2", + "@react-router/dev": "^7.6.3", "@swc-node/register": "~1.9.1", "@swc/cli": "~0.6.0", "@swc/core": "~1.5.7", From 05df4d101376b8da7b44ff398cbff39fb367d3f3 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Sat, 28 Jun 2025 00:59:18 +0200 Subject: [PATCH 08/10] feat: merged services and providers the services and providers arrays in modules have now been merged to a single prviders array. The still work the same though. it's just simpler with less API BREAKING CHANGE: services array removed #20 --- libs/core/README.md | 24 +++++++++++++--- libs/core/src/container.test.ts | 4 +-- libs/core/src/container.ts | 13 ++------- libs/core/src/decorators/module.test.ts | 7 ++--- libs/core/src/decorators/module.ts | 4 +-- libs/core/src/index.ts | 2 ++ libs/core/src/module.test.ts | 7 ++--- libs/core/src/types.test.ts | 37 ------------------------- libs/core/src/types.ts | 1 - 9 files changed, 34 insertions(+), 65 deletions(-) diff --git a/libs/core/README.md b/libs/core/README.md index 6937d28..e5c8de9 100644 --- a/libs/core/README.md +++ b/libs/core/README.md @@ -1,11 +1,27 @@ # @nexusdi/core +
+ NexusDI Logo +
+

A modern, lightweight dependency injection container for TypeScript with native decorators, inspired by industry-leading frameworks.

+

The DI library that doesn't make you want to inject yourself with coffee β˜•

+
+ +
+ [![npm version](https://img.shields.io/npm/v/@nexusdi/core.svg)](https://www.npmjs.com/package/@nexusdi/core) -[![build status](https://github.com/NexusDI/core/actions/workflows/ci.yml/badge.svg)](https://github.com/NexusDI/core/actions/workflows/ci.yml) -[![license](https://img.shields.io/npm/l/@nexusdi/core.svg)](./LICENSE) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nexusdi/core/ci.yml) +![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/nexusdi/core) + +![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/%40nexusdi/core) +![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/%40nexusdi%2Fcore) +![Source language](https://img.shields.io/badge/language-TypeScript-blue) + +[![npm downloads](https://img.shields.io/npm/dm/@nexusdi/core.svg)](https://www.npmjs.com/package/@nexusdi/core) +![GitHub License](https://img.shields.io/github/license/NexusDI/core) +[![GitHub stars](https://img.shields.io/github/stars/NexusDI/core.svg?style=social&label=Star)](https://github.com/NexusDI/core) -NexusDI offers a fast, modern DI container for TypeScript projects. -This package delivers the core engine used by all NexusDI modules and extensions. +
## Installation diff --git a/libs/core/src/container.test.ts b/libs/core/src/container.test.ts index 68a814f..40f315a 100644 --- a/libs/core/src/container.test.ts +++ b/libs/core/src/container.test.ts @@ -241,7 +241,7 @@ describe('Nexus', () => { } } @Module({ - services: [LoggerService, UserServiceWithLogger], + providers: [LoggerService, UserServiceWithLogger], }) class AppModule {} nexus.set(AppModule); @@ -403,7 +403,7 @@ describe('Nexus', () => { */ it('should throw if registering undecorated class in a module', () => { class NotAService {} - @Module({ services: [NotAService] }) + @Module({ providers: [NotAService] }) class Mod {} expect(() => nexus.set(Mod)).toThrowError(InvalidService); }); diff --git a/libs/core/src/container.ts b/libs/core/src/container.ts index 9098223..312965e 100644 --- a/libs/core/src/container.ts +++ b/libs/core/src/container.ts @@ -149,7 +149,6 @@ export class Nexus implements IContainer { set(moduleConfig: { providers?: ModuleProvider[]; imports?: Constructor[]; - services?: Constructor[]; exports?: TokenType[]; }): void; set(tokenOrModuleOrConfig: any, providerOrNothing?: any): void { @@ -180,9 +179,7 @@ export class Nexus implements IContainer { if ( tokenOrModuleOrConfig && typeof tokenOrModuleOrConfig === 'object' && - (tokenOrModuleOrConfig.services || - tokenOrModuleOrConfig.providers || - tokenOrModuleOrConfig.imports) + (tokenOrModuleOrConfig.providers || tokenOrModuleOrConfig.imports) ) { this.processModuleConfig(tokenOrModuleOrConfig); return; @@ -277,21 +274,15 @@ export class Nexus implements IContainer { * Process module configuration (shared between setModule and registerDynamicModule) */ private processModuleConfig(moduleConfig: { - services?: Constructor[]; providers?: ModuleProvider[]; imports?: Constructor[]; + exports?: TokenType[]; }): void { if (moduleConfig.imports) { for (const importedModule of moduleConfig.imports) { this.set(importedModule); } } - if (moduleConfig.services) { - for (const serviceClass of moduleConfig.services) { - const { token, provider } = this.normalizeProvider(serviceClass); - this.setProvider(token as TokenType, provider as Provider); - } - } if (moduleConfig.providers) { for (const provider of moduleConfig.providers) { const { token, provider: normProvider } = this.normalizeProvider( diff --git a/libs/core/src/decorators/module.test.ts b/libs/core/src/decorators/module.test.ts index 00df177..ab974aa 100644 --- a/libs/core/src/decorators/module.test.ts +++ b/libs/core/src/decorators/module.test.ts @@ -13,12 +13,11 @@ describe('@Module', () => { }); it('should add module metadata to class', () => { - @Module({ imports: [], services: [], providers: [], exports: [] }) + @Module({ imports: [], providers: [], exports: [] }) class TestModule {} const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toEqual({ imports: [], - services: [], providers: [], exports: [], }); @@ -26,10 +25,10 @@ describe('@Module', () => { it('should handle module with services', () => { class TestService {} - @Module({ services: [TestService] }) + @Module({ providers: [TestService] }) class TestModule {} const metadata = getMetadata(TestModule, METADATA_KEYS.MODULE_METADATA); - expect(metadata.services).toEqual([TestService]); + expect(metadata.providers).toEqual([TestService]); }); it('should handle module with providers', () => { diff --git a/libs/core/src/decorators/module.ts b/libs/core/src/decorators/module.ts index 12be6bd..b4045b1 100644 --- a/libs/core/src/decorators/module.ts +++ b/libs/core/src/decorators/module.ts @@ -3,9 +3,9 @@ import { METADATA_KEYS } from '../constants'; import { setMetadata } from '../helpers'; /** - * Decorator that marks a class as a DI module, allowing you to group providers, services, and imports. + * Decorator that marks a class as a DI module, allowing you to group providers and imports. * - * Use this to define a module in NexusDI. Modules can import other modules, provide services, and export tokens. + * Use this to define a module in NexusDI. Modules can import other modules, provide services/providers, and export tokens. * * #### Usage * ```typescript diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index ac07620..0ddf773 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -49,3 +49,5 @@ import { Nexus } from './container'; export default Nexus; export * from './guards'; + +export { setMetadata, getMetadata } from './helpers'; diff --git a/libs/core/src/module.test.ts b/libs/core/src/module.test.ts index 2125ca2..96a3ec8 100644 --- a/libs/core/src/module.test.ts +++ b/libs/core/src/module.test.ts @@ -25,8 +25,7 @@ class TestDynamicModule extends DynamicModule { * This ensures the decorator works for standard modules, not just DynamicModule. */ @Module({ - providers: [class ProviderA {}], - services: [class ServiceA {}], + providers: [class ProviderA {}, class ServiceA {}], imports: [], exports: [], }) @@ -112,7 +111,7 @@ describe('Module decorator', () => { const metadata = getMetadata(BasicModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); expect(metadata.providers).toBeInstanceOf(Array); - expect(metadata.services).toBeInstanceOf(Array); + expect(metadata.providers).toBeInstanceOf(Array); expect(metadata.imports).toBeInstanceOf(Array); expect(metadata.exports).toBeInstanceOf(Array); }); @@ -194,7 +193,7 @@ describe('Module edge cases', () => { const metadata = getMetadata(EmptyModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); expect(metadata.providers ?? []).toBeInstanceOf(Array); - expect(metadata.services ?? []).toBeInstanceOf(Array); + expect(metadata.providers ?? []).toBeInstanceOf(Array); expect(metadata.imports ?? []).toBeInstanceOf(Array); expect(metadata.exports ?? []).toBeInstanceOf(Array); }); diff --git a/libs/core/src/types.test.ts b/libs/core/src/types.test.ts index ed03b66..35eed08 100644 --- a/libs/core/src/types.test.ts +++ b/libs/core/src/types.test.ts @@ -3,7 +3,6 @@ import { Token } from './token'; import { type TokenType, type ModuleProvider, - type ServiceConfig, type ModuleConfig, type InjectionMetadata, type ProviderConfig, @@ -79,38 +78,6 @@ describe('Types', () => { }); }); - // ServiceConfig group: Ensures service configuration is flexible and robust - describe('ServiceConfig', () => { - /** - * Test: ServiceConfig with token - * Validates: Structure and values - * Value: Ensures DI can register services with explicit tokens and singleton flag - */ - it('should define service config with token', () => { - const token = new Token('SERVICE_TOKEN'); - const config: { token: TokenType; singleton: boolean } = { - token, - singleton: true, - }; - expect(config.token).toBe(token); - expect(config.singleton).toBe(true); - }); - - /** - * Test: ServiceConfig without token - * Validates: Structure and default values - * Value: Allows for simple singleton/non-singleton service registration - */ - it('should define service config without token', () => { - const config: ServiceConfig = { - singleton: false, - }; - - expect(config.token).toBeUndefined(); - expect(config.singleton).toBe(false); - }); - }); - // ModuleConfig group: Ensures module configuration is flexible and robust describe('ModuleConfig', () => { /** @@ -124,17 +91,14 @@ describe('Types', () => { const token = new Token('PROVIDER_TOKEN'); const config: { imports: any[]; - services: any[]; providers: { token: TokenType; useClass: typeof TestService }[]; exports: TokenType[]; } = { imports: [TestModule], - services: [TestService], providers: [{ token, useClass: TestService }], exports: [token as unknown as TokenType], }; expect(config.imports).toEqual([TestModule]); - expect(config.services).toEqual([TestService]); expect(config.providers).toHaveLength(1); expect(config.exports).toEqual([token as unknown as TokenType]); }); @@ -148,7 +112,6 @@ describe('Types', () => { const config: ModuleConfig = {}; expect(config.imports).toBeUndefined(); - expect(config.services).toBeUndefined(); expect(config.providers).toBeUndefined(); expect(config.exports).toBeUndefined(); }); diff --git a/libs/core/src/types.ts b/libs/core/src/types.ts index 805815f..ec402f8 100644 --- a/libs/core/src/types.ts +++ b/libs/core/src/types.ts @@ -63,7 +63,6 @@ export type ProviderConfig = { */ export type ModuleConfig = { imports?: Constructor[]; - services?: Constructor[]; providers?: ModuleProvider[]; exports?: TokenType[]; }; From 75111527d4a9b43c9f72ae92fd75142d3eb80b6d Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Sat, 28 Jun 2025 01:15:38 +0200 Subject: [PATCH 09/10] docs: update for new features in 0.3 --- README.md | 19 ++-- docs/docs/advanced/circular-dependencies.md | 8 +- docs/docs/advanced/module-inheritance.md | 51 ++++++----- docs/docs/best-practices.md | 3 +- docs/docs/container/api-reference.md | 47 +++++----- docs/docs/contributing/docs.md | 14 ++- docs/docs/getting-started.md | 8 +- docs/docs/intro.md | 9 +- docs/docs/modules/dynamic-modules.md | 36 +++----- docs/docs/modules/module-basics.md | 48 +++++----- docs/docs/modules/module-patterns.md | 97 +++++++++++++-------- docs/docs/modules/modules.md | 2 +- docs/docs/performance.md | 12 +-- docs/docs/providers-and-services.md | 59 ++++++++----- docs/docs/roadmap.md | 27 +++--- docs/docs/testing.md | 14 +-- 16 files changed, 263 insertions(+), 191 deletions(-) diff --git a/README.md b/README.md index c4d1b0a..cc3ec00 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,24 @@ # NexusDI
- NexusDI Logo + NexusDI Logo
-

A modern, lightweight dependency injection container for TypeScript with decorator support, inspired by industry-leading frameworks.

+

A modern, lightweight dependency injection container for TypeScript with native decorators, inspired by industry-leading frameworks.

The DI library that doesn't make you want to inject yourself with coffee β˜•

-[![npm version](https://badge.fury.io/js/%40nexusdi%2Fcore.svg)](https://badge.fury.io/js/%40nexusdi%2Fcore) +[![npm version](https://img.shields.io/npm/v/@nexusdi/core.svg)](https://www.npmjs.com/package/@nexusdi/core) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nexusdi/core/ci.yml) +![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/nexusdi/core) + +![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/%40nexusdi/core) +![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/%40nexusdi%2Fcore) +![Source language](https://img.shields.io/badge/language-TypeScript-blue) + [![npm downloads](https://img.shields.io/npm/dm/@nexusdi/core.svg)](https://www.npmjs.com/package/@nexusdi/core) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) -[![Bundle Size](https://img.shields.io/bundlephobia/min/@nexusdi/core)](https://bundlephobia.com/package/@nexusdi/core) +![GitHub License](https://img.shields.io/github/license/NexusDI/core) [![GitHub stars](https://img.shields.io/github/stars/NexusDI/core.svg?style=social&label=Star)](https://github.com/NexusDI/core)
@@ -88,7 +93,7 @@ interface DatabaseConfig { const DATABASE_CONFIG = Symbol('DATABASE_CONFIG'); @Module({ - services: [DatabaseService], // Simplified format - uses @Service decorator token + providers: [DatabaseService], // Simplified format - uses @Service decorator token }) class DatabaseModule extends DynamicModule { protected readonly configToken = DATABASE_CONFIG; diff --git a/docs/docs/advanced/circular-dependencies.md b/docs/docs/advanced/circular-dependencies.md index f3b0484..7dc1d73 100644 --- a/docs/docs/advanced/circular-dependencies.md +++ b/docs/docs/advanced/circular-dependencies.md @@ -10,7 +10,7 @@ Learn how to identify, prevent, and resolve circular dependencies in NexusDI. Li ## What Are Circular Dependencies? -A circular dependency occurs when two or more services depend on each other, either directly or indirectly. This creates a dependency cycle that the container cannot resolve. +A circular dependency occurs when two or more providers depend on each other, either directly or indirectly. This creates a dependency cycle that the container cannot resolve. ```typescript // ❌ Direct circular dependency @@ -184,7 +184,7 @@ class EmailService { ## Prevention Strategies -- **Single Responsibility Principle**: Each service should have one reason to change +- **Single Responsibility Principle**: Each provider should have one reason to change - **Dependency Inversion**: Depend on abstractions, not concretions - **Interface Segregation**: Use small, focused interfaces - **Event-Driven Architecture**: Use events for communication @@ -209,7 +209,7 @@ describe('Circular Dependency Detection', () => { }).toThrow('Circular dependency detected'); }); - it('should resolve services without circular dependencies', () => { + it('should resolve providers without circular dependencies', () => { container.set(USER_SERVICE, { useClass: UserService }); container.set(EMAIL_SERVICE, { useClass: EmailService }); @@ -226,4 +226,4 @@ describe('Circular Dependency Detection', () => { - **[Performance Tuning](performance-tuning.md)** - Optimize container performance - **[Testing](../testing.md)** - Test your dependency injection setup -Remember: Design your services with clear boundaries and loose coupling to avoid circular dependency traps! πŸ”„βœ¨ +Remember: Design your providers with clear boundaries and loose coupling to avoid circular dependency traps! πŸ”„βœ¨ diff --git a/docs/docs/advanced/module-inheritance.md b/docs/docs/advanced/module-inheritance.md index a2c53ee..99d64a6 100644 --- a/docs/docs/advanced/module-inheritance.md +++ b/docs/docs/advanced/module-inheritance.md @@ -8,7 +8,7 @@ Module inheritance lets you build on existing modules by subclassing them, but b ## Why Inherit Modules? πŸ€– -Module inheritance lets you extend existing modules with new services or configuration, making it easy to reuse and adapt functionality for different scenarios. It's like giving your modules an upgrade! +Module inheritance lets you extend existing modules with new providers or configuration, making it easy to reuse and adapt functionality for different scenarios. It's like giving your modules an upgrade! ## Basic Example: Parent and Subclass @@ -34,14 +34,21 @@ class AuditingModule extends LoggingModule {} ## Pitfall: Metadata Inheritance -By default, TypeScript's `Reflect.getMetadata` will return metadata from the parent if the subclass isn't decorated. This can lead to subtle bugs: +By default, both TypeScript's `Reflect.getMetadata` and native `Symbol.metadata` (as accessed via NexusDI's `getMetadata`) will return metadata from the parent if the subclass isn't decorated. This can lead to subtle bugs: ```typescript -const parentMeta = Reflect.getMetadata('nexusdi:module', LoggingModule); // { providers: [LoggerService] } -const childMeta = Reflect.getMetadata('nexusdi:module', ExtendedLoggingModule); // { providers: [LoggerService] } (inherited!) +import { getMetadata, METADATA_KEYS } from '@nexusdi/core'; + +const parentMeta = getMetadata(LoggingModule, METADATA_KEYS.MODULE_METADATA); // { providers: [LoggerService] } +const childMeta = getMetadata( + ExtendedLoggingModule, + METADATA_KEYS.MODULE_METADATA +); // { providers: [LoggerService] } (inherited!) ``` -This means **subclasses without `@Module` will appear to have the parent's metadata**, but NexusDI expects every module to be explicitly decorated. The real risk is that your subclass will only include the parent's providers and configuration β€” any new configuration added in the child will be ignored unless you decorate the subclass with `@Module`. To add new services or providers to a child module, you must decorate it as a module. +> **Note:** This inheritance behavior applies to both legacy `Reflect.getMetadata` and modern native `Symbol.metadata` (as used by NexusDI's `getMetadata`). + +This means **subclasses without `@Module` will appear to have the parent's metadata**, but NexusDI expects every module to be explicitly decorated. The real risk is that your subclass will only include the parent's providers and configuration β€” any new configuration added in the child will be ignored unless you decorate the subclass with `@Module`. To add new providers or configuration to a child module, you must decorate it as a module. ## Best Practice: Always Decorate Subclasses @@ -49,10 +56,12 @@ If you want a subclass to be a module, always decorate it: ```typescript @Service() -class AuditService {} +class AuditService { + constructor(private readonly logger: LoggerService) {} +} @Module({ - providers: [], + providers: [AuditService], }) class AuditingModule extends LoggingModule {} ``` @@ -62,29 +71,27 @@ class AuditingModule extends LoggingModule {} Here's how you can test correct and incorrect inheritance: ```typescript -import { Module, Service } from '@nexusdi/core'; -import { METADATA_KEYS } from '@nexusdi/core/src/types'; +import { Module, Service, METADATA_KEYS, getMetadata } from '@nexusdi/core'; describe('Module inheritance', () => { @Service() class LoggerService {} + @Module({ providers: [LoggerService] }) class LoggingModule {} + class ExtendedLoggingModule extends LoggingModule {} it('should not have metadata on subclass if not decorated', () => { - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - ExtendedLoggingModule + const metadata = getMetadata( + ExtendedLoggingModule, + METADATA_KEYS.MODULE_METADATA ); expect(metadata).toBeUndefined(); }); it('should have metadata on parent if decorated', () => { - const metadata = Reflect.getMetadata( - METADATA_KEYS.MODULE_METADATA, - LoggingModule - ); + const metadata = getMetadata(LoggingModule, METADATA_KEYS.MODULE_METADATA); expect(metadata).toBeDefined(); }); }); @@ -94,15 +101,19 @@ describe('Module inheritance', () => { ```typescript @Module({ - services: [UserService], - providers: [{ token: USER_CONFIG, useValue: { feature: 'basic' } }], + providers: [ + { token: USER_SERVICE, useClass: UserService }, + { token: USER_CONFIG, useValue: { feature: 'basic' } }, + ], }) class UserModule {} // Add premium features by extending and redecorating @Module({ - services: [PremiumUserService], - providers: [{ token: USER_CONFIG, useValue: { feature: 'premium' } }], + providers: [ + { token: USER_SERVICE, useClass: PremiumUserService }, + { token: USER_CONFIG, useValue: { feature: 'premium' } }, + ], }) class PremiumUserModule extends UserModule {} ``` diff --git a/docs/docs/best-practices.md b/docs/docs/best-practices.md index f686935..c7a34ee 100644 --- a/docs/docs/best-practices.md +++ b/docs/docs/best-practices.md @@ -55,8 +55,7 @@ Keep your code organized and modular: ```typescript @Module({ - services: [UserService, UserRepository, UserValidator], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [UserService, UserRepository, UserValidator], }) class UserModule {} ``` diff --git a/docs/docs/container/api-reference.md b/docs/docs/container/api-reference.md index fb7f89f..4d8929f 100644 --- a/docs/docs/container/api-reference.md +++ b/docs/docs/container/api-reference.md @@ -12,10 +12,14 @@ This article documents all public methods available on the `Nexus` container cla ### `get(token: TokenType): T` -Retrieve an instance for the given token. Throws if the provider is not registered. +Retrieve an instance for the given token from the container's registry. Throws if the provider is not registered. + +- If the token is registered as a singleton, always returns the same instance. +- If the token is registered as a factory or value, returns the result/value. +- Throws if the provider is not registered. ```typescript -const userService = container.get(UserService); +const userService = container.get(USER_SERVICE); ``` --- @@ -25,7 +29,7 @@ const userService = container.get(UserService); Check if a provider is registered for the given token. ```typescript -if (container.has(UserService)) { +if (container.has(USER_SERVICE)) { // ... } ``` @@ -36,19 +40,22 @@ if (container.has(UserService)) { Register a provider, module, or dynamic module configuration. The container will automatically detect the type and handle it appropriately. +- You must register a decorated class (with `@Service` or `@Provider`) or a valid provider object (`{ useClass, useValue, useFactory }`). +- Tokens must be a `Token`, symbol, or class constructor (no string tokens). + ```typescript // Register a provider (class, value, or factory) -container.set(UserService, UserService); -container.set('CONFIG', { useValue: config }); -container.set(Logger, { useFactory: () => new Logger() }); +container.set(USER_SERVICE, UserService); // UserService must be decorated +container.set(LOGGER, { useValue: new ConsoleLogger() }); +container.set(DATABASE, { useFactory: () => new PostgresDatabase() }); // Register a module class decorated with @Module container.set(AppModule); // Register a dynamic module configuration object container.set({ - services: [UserService], - providers: [{ token: 'CONFIG', useValue: config }], + providers: [UserService], + providers: [{ token: LOGGER, useValue: new ConsoleLogger() }], }); ``` @@ -56,27 +63,33 @@ container.set({ ### `resolve(target: new (...args: any[]) => T): T` -Create a new instance of a class, injecting all dependencies (constructor and property injection). +Instantiates a new instance of the given class, resolving and injecting all dependencies. + +- Only accepts a class constructor. Unlike `get`, this does not require the class to be registered as a provider and always returns a new instance. +- Useful for transient or ad-hoc objects that are not managed by the container's provider registry. +- Throws if dependencies cannot be resolved or if a non-constructor is passed. ```typescript -const userService = container.resolve(UserService); +const userService = container.resolve(UserService); // UserService must be a class constructor ``` --- ### `createChildContainer(): Nexus` -Create a new child container that inherits all providers, modules, and instances from the parent. +Creates a new child container that inherits from the current container. Useful for request-scoped or session-scoped dependencies. ```typescript const requestContainer = container.createChildContainer(); +requestContainer.set(REQUEST_ID, { useValue: generateRequestId() }); +const userService = requestContainer.get(USER_SERVICE); ``` --- -### `clear(): void` +### `clear()` -Remove all registered providers, modules, and instances from the container. +Clears all registered providers and instances from the container. ```typescript container.clear(); @@ -97,11 +110,3 @@ console.log('Modules:', modules); --- For more advanced usage and patterns, see the [Advanced](../advanced/advanced.md) section. - -### `setModule(moduleClass: new (...args: any[]) => any): void` - -**@deprecated** Use the unified `set()` method instead. - -### `registerDynamicModule(moduleConfig: { services?: Function[]; providers?: ModuleProvider[]; imports?: Function[] }): void` - -**@deprecated** Use the unified `set()` method instead. diff --git a/docs/docs/contributing/docs.md b/docs/docs/contributing/docs.md index b700084..4f7c362 100644 --- a/docs/docs/contributing/docs.md +++ b/docs/docs/contributing/docs.md @@ -110,8 +110,11 @@ Modules are a powerful way to organize your dependency injection setup in NexusD ```typescript @Module({ - services: [UserService, UserRepository], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [ + UserService, + UserRepository, + { token: DATABASE, useClass: PostgresDatabase }, + ], }) class UserModule {} ``` @@ -129,8 +132,11 @@ Just as in World of Warcraft where you have different guild departments, modules ```typescript @Module({ - services: [UserService, UserRepository], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [ + UserService, + UserRepository, + { token: DATABASE, useClass: PostgresDatabase }, + ], }) class UserModule {} ``` diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index a7bee0f..adea04d 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -4,7 +4,7 @@ sidebar_position: 2 # Getting Started πŸš€ -Welcome to NexusDI! This guide will help you get started with dependency injection using tokens and interfaces. We'll walk through the basics step by stepβ€”no need to reroute power from life support, just follow along! +This guide will help you get started with dependency injection using tokens and interfaces. We'll walk through the basics step by stepβ€”no need to reroute power from life support, just follow along! ## Why Dependency Injection? @@ -68,7 +68,7 @@ For detailed explanations and real-world examples, see **[Dependency Injection]( ## Installation ```bash -npm install @nexusdi/core reflect-metadata +npm install @nexusdi/core ``` ## TypeScript Configuration @@ -81,13 +81,15 @@ To use decorators and metadata with NexusDI, make sure your `tsconfig.json` incl "target": "es2022", "lib": ["es2022", "esnext.decorators"], "experimentalDecorators": true, - "emitDecoratorMetadata": true + "useDefineForClassFields": true // Defaults to 'true' in TypeScript 5.2+ } } ``` > πŸ› οΈ **Tip:** These settings ensure that TypeScript emits the correct decorator and metadata code for NexusDI. If you see errors about decorators or metadata, double-check your `tsconfig.json`. +> **Note:** NexusDI v0.3.0+ uses native decorator metadata (TypeScript 5.2+). You do not need to install or import `reflect-metadata`. + ## Basic Usage with Tokens and Interfaces NexusDI promotes using tokens with interfaces for better flexibility and maintainability. Here's how to get started: diff --git a/docs/docs/intro.md b/docs/docs/intro.md index e54fbef..83976f7 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -1,14 +1,15 @@ --- sidebar_position: 1 +title: Welcome to NexusDI! πŸŽ‰ --- -# Welcome to NexusDI! πŸŽ‰ +# Welcome to NexusDI! Welcome to NexusDI - a dependency injection library that makes sense! No more wrestling with verbose containers or fighting with type systems. NexusDI is here to make your code cleaner, your tests easier, and your development experience smoother. ## What's This All About? -NexusDI is a powerful, lightweight, and ergonomic dependency injection (DI) container for TypeScript and JavaScript. It helps you organize your code without being overly opinionated about how you do it. Think of it as the Millennium Falcon of DI libraries - it may not look like much, but it's got it where it counts. +NexusDI is a powerful, lightweight, and ergonomic dependency injection (DI) container for TypeScript and JavaScript. It helps you organize your code without being overly opinionated about how you do it. It's the Millennium Falcon of DI libraries - it may not look like much, but it's got it where it counts. ## Why NexusDI? πŸš€ @@ -23,7 +24,7 @@ NexusDI is a powerful, lightweight, and ergonomic dependency injection (DI) cont First, install NexusDI: ```bash -npm install @nexusdi/core reflect-metadata +npm install @nexusdi/core ``` Now, let's create a simple example: @@ -54,3 +55,5 @@ That's it! In just a few lines, you've got a fully functional DI setup. Clean, p Now that you've had a taste, explore the rest of the documentation to learn about modules, advanced features, and best practices. We promise it only gets better from here! Let's build something awesome together! ✨ + +> **Note:** NexusDI vX.X+ uses native decorator metadata (TypeScript 5.2+). You do not need to install or import `reflect-metadata`, and you do not need `emitDecoratorMetadata` in your tsconfig. Only `experimentalDecorators` and `useDefineForClassFields` (default in TypeScript 5.2+) are required. diff --git a/docs/docs/modules/dynamic-modules.md b/docs/docs/modules/dynamic-modules.md index 89a6bb4..3eb3815 100644 --- a/docs/docs/modules/dynamic-modules.md +++ b/docs/docs/modules/dynamic-modules.md @@ -35,14 +35,12 @@ interface DatabaseConfig { } @Module({ - services: [DatabaseService], - providers: [{ token: DATABASE_CONFIG, useValue: {} }], + providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: {} }], }) class DatabaseModule extends DynamicModule { protected readonly configToken = DATABASE_CONFIG; protected readonly moduleConfig = { - services: [DatabaseService], - providers: [{ token: DATABASE_CONFIG, useValue: {} }], + providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: {} }], }; } @@ -81,8 +79,8 @@ For now, you can achieve similar functionality using the current module system: ```typescript // Development module @Module({ - services: [DatabaseService], providers: [ + DatabaseService, { token: DATABASE_CONFIG, useValue: { @@ -97,8 +95,8 @@ class DevelopmentDatabaseModule {} // Production module @Module({ - services: [DatabaseService], providers: [ + DatabaseService, { token: DATABASE_CONFIG, useValue: { @@ -150,14 +148,12 @@ This pattern will be supported with dynamic module configuration in future relea ```typescript @Module({ - services: [LoggerService], - providers: [{ token: LOG_CONFIG, useValue: {} }], + providers: [LoggerService, { token: LOG_CONFIG, useValue: {} }], }) class LoggingModule extends DynamicModule { protected readonly configToken = LOG_CONFIG; protected readonly moduleConfig = { - services: [LoggerService], - providers: [{ token: LOG_CONFIG, useValue: {} }], + providers: [LoggerService, { token: LOG_CONFIG, useValue: {} }], }; } @@ -210,14 +206,12 @@ interface EmailConfig { } @Module({ - services: [EmailService], - providers: [{ token: EMAIL_CONFIG, useValue: {} }], + providers: [EmailService, { token: EMAIL_CONFIG, useValue: {} }], }) class EmailModule extends DynamicModule { protected readonly configToken = EMAIL_CONFIG; protected readonly moduleConfig = { - services: [EmailService], - providers: [{ token: EMAIL_CONFIG, useValue: {} }], + providers: [EmailService, { token: EMAIL_CONFIG, useValue: {} }], }; } @@ -264,8 +258,7 @@ This pattern will be supported with dynamic module configuration in future relea ```typescript @Module({ - services: [AppService], - providers: [{ token: APP_CONFIG, useValue: {} }], + providers: [AppService, { token: APP_CONFIG, useValue: {} }], }) class AppModule extends DynamicModule<{ database: DatabaseConfig; @@ -274,8 +267,7 @@ class AppModule extends DynamicModule<{ }> { protected readonly configToken = APP_CONFIG; protected readonly moduleConfig = { - services: [AppService], - providers: [{ token: APP_CONFIG, useValue: {} }], + providers: [AppService, { token: APP_CONFIG, useValue: {} }], imports: [ DatabaseModule.config({} as DatabaseConfig), EmailModule.config({} as EmailConfig), @@ -315,14 +307,12 @@ Configuration validation will be supported with dynamic module configuration in ```typescript @Module({ - services: [DatabaseService], - providers: [{ token: DATABASE_CONFIG, useValue: {} }], + providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: {} }], }) class DatabaseModule extends DynamicModule { protected readonly configToken = DATABASE_CONFIG; protected readonly moduleConfig = { - services: [DatabaseService], - providers: [{ token: DATABASE_CONFIG, useValue: {} }], + providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: {} }], }; static config(config: DatabaseConfig) { @@ -395,8 +385,8 @@ describe('DatabaseModule', () => { // Use a test-specific module @Module({ - services: [DatabaseService], providers: [ + DatabaseService, { token: DATABASE_CONFIG, useValue: { diff --git a/docs/docs/modules/module-basics.md b/docs/docs/modules/module-basics.md index f32b2c7..c9e0c3e 100644 --- a/docs/docs/modules/module-basics.md +++ b/docs/docs/modules/module-basics.md @@ -32,14 +32,16 @@ container.set(LOGGER, { useClass: Logger }); // You can organize into focused modules @Module({ - services: [UserService, UserRepository], - providers: [{ token: DATABASE, useClass: Database }], + providers: [UserService, UserRepository], }) class UserModule {} @Module({ - services: [EmailService, NotificationService], - providers: [{ token: EMAIL_CONFIG, useValue: emailConfig }], + providers: [ + EmailService, + NotificationService, + { token: EMAIL_CONFIG, useValue: emailConfig }, + ], }) class NotificationModule {} ``` @@ -51,8 +53,12 @@ Modules can be reused across different applications or parts of your application ```typescript // Reusable authentication module @Module({ - services: [AuthService, JwtService, PasswordService], - providers: [{ token: AUTH_CONFIG, useValue: authConfig }], + providers: [ + AuthService, + JwtService, + PasswordService, + { token: AUTH_CONFIG, useValue: authConfig }, + ], }) class AuthModule {} @@ -73,7 +79,7 @@ Modules make it easier to test specific parts of your application: ```typescript // Test with mocked dependencies @Module({ - services: [UserService], + providers: [UserService], providers: [ { token: DATABASE, useValue: mockDatabase }, { token: LOGGER, useValue: mockLogger }, @@ -91,7 +97,7 @@ Modules can encapsulate configuration and environment-specific settings: ```typescript @Module({ - services: [DatabaseService], + providers: [DatabaseService], providers: [ { token: DATABASE_CONFIG, @@ -129,8 +135,7 @@ class UserService implements IUserService { // Create the module @Module({ - services: [UserService], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [UserService, { token: DATABASE, useClass: PostgresDatabase }], }) export class UserModule {} ``` @@ -141,14 +146,15 @@ Modules can import other modules to compose functionality: ```typescript @Module({ - services: [AuthService], - providers: [{ token: JWT_SECRET, useValue: process.env.JWT_SECRET }], + providers: [ + AuthService, + { token: JWT_SECRET, useValue: process.env.JWT_SECRET }, + ], }) class AuthModule {} @Module({ - services: [UserService], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [UserService, { token: DATABASE, useClass: PostgresDatabase }], imports: [AuthModule], // Import the auth module }) class UserModule {} @@ -160,8 +166,11 @@ Export services to make them available to other modules: ```typescript @Module({ - services: [DatabaseService, ConnectionPool], - providers: [{ token: DATABASE_CONFIG, useValue: dbConfig }], + providers: [ + DatabaseService, + ConnectionPool, + { token: DATABASE_CONFIG, useValue: dbConfig }, + ], exports: [DATABASE_SERVICE], // Export for other modules to use }) class DatabaseModule {} @@ -195,19 +204,18 @@ Modules can depend on each other through imports: ```typescript @Module({ - services: [DatabaseService], - providers: [{ token: DATABASE_CONFIG, useValue: dbConfig }], + providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: dbConfig }], }) class DatabaseModule {} @Module({ - services: [UserService], + providers: [UserService], imports: [DatabaseModule], // Depends on DatabaseModule }) class UserModule {} @Module({ - services: [OrderService], + providers: [OrderService], imports: [DatabaseModule, UserModule], // Depends on both }) class OrderModule {} diff --git a/docs/docs/modules/module-patterns.md b/docs/docs/modules/module-patterns.md index 3d1799b..aa72bdc 100644 --- a/docs/docs/modules/module-patterns.md +++ b/docs/docs/modules/module-patterns.md @@ -15,23 +15,30 @@ Organize by application features: ```typescript // User feature module @Module({ - services: [UserService, UserRepository, UserValidator], - providers: [{ token: USER_CONFIG, useValue: userConfig }], + providers: [ + UserService, + UserRepository, + UserValidator, + { token: USER_CONFIG, useValue: userConfig }, + ], }) class UserModule {} // Order feature module @Module({ - services: [OrderService, OrderRepository, PaymentService], - providers: [{ token: PAYMENT_GATEWAY, useClass: StripeGateway }], + providers: [ + OrderService, + OrderRepository, + PaymentService, + { token: PAYMENT_GATEWAY, useClass: StripeGateway }, + ], }) class OrderModule {} // Main application module @Module({ imports: [UserModule, OrderModule], - services: [AppService], - providers: [{ token: APP_CONFIG, useValue: appConfig }], + providers: [AppService, { token: APP_CONFIG, useValue: appConfig }], }) class AppModule {} ``` @@ -42,20 +49,29 @@ Separate infrastructure concerns: ```typescript @Module({ - services: [DatabaseService, ConnectionPool], - providers: [{ token: DATABASE_CONFIG, useValue: dbConfig }], + providers: [ + DatabaseService, + ConnectionPool, + { token: DATABASE_CONFIG, useValue: dbConfig }, + ], }) class DatabaseModule {} @Module({ - services: [LoggerService, LogFormatter], - providers: [{ token: LOG_LEVEL, useValue: process.env.LOG_LEVEL }], + providers: [ + LoggerService, + LogFormatter, + { token: LOG_LEVEL, useValue: process.env.LOG_LEVEL }, + ], }) class LoggingModule {} @Module({ - services: [EmailService, TemplateEngine], - providers: [{ token: SMTP_CONFIG, useValue: smtpConfig }], + providers: [ + EmailService, + TemplateEngine, + { token: SMTP_CONFIG, useValue: smtpConfig }, + ], }) class EmailModule {} ``` @@ -67,8 +83,9 @@ Different modules for different environments: ```typescript // Development module @Module({ - services: [DevLogger, DevDatabase], providers: [ + DevLogger, + DevDatabase, { token: LOG_LEVEL, useValue: 'debug' }, { token: DATABASE_URL, useValue: 'sqlite://dev.db' }, ], @@ -77,8 +94,9 @@ class DevelopmentModule {} // Production module @Module({ - services: [ProductionLogger, PostgresDatabase], providers: [ + ProductionLogger, + PostgresDatabase, { token: LOG_LEVEL, useValue: 'info' }, { token: DATABASE_URL, useValue: process.env.DATABASE_URL }, ], @@ -129,8 +147,8 @@ describe('UserModule', () => { ```typescript // Create a test module with mocked dependencies @Module({ - services: [UserService], providers: [ + UserService, { token: DATABASE, useValue: mockDatabase }, { token: LOGGER, useValue: mockLogger }, ], @@ -164,15 +182,22 @@ Each module should have a single, well-defined responsibility: ```typescript // βœ… Good - focused on user management @Module({ - services: [UserService, UserRepository, UserValidator], - providers: [{ token: USER_CONFIG, useValue: userConfig }], + providers: [ + UserService, + UserRepository, + UserValidator, + { token: USER_CONFIG, useValue: userConfig }, + ], }) class UserModule {} // ❌ Bad - mixing unrelated concerns @Module({ - services: [UserService, EmailService, PaymentService, LoggerService], providers: [ + UserService, + EmailService, + PaymentService, + LoggerService, { token: USER_CONFIG, useValue: userConfig }, { token: EMAIL_CONFIG, useValue: emailConfig }, { token: PAYMENT_CONFIG, useValue: paymentConfig }, @@ -188,14 +213,14 @@ Make module dependencies explicit through imports: ```typescript // βœ… Good - explicit dependencies @Module({ - services: [UserService], + providers: [UserService], imports: [DatabaseModule, LoggingModule], }) class UserModule {} // ❌ Bad - hidden dependencies @Module({ - services: [UserService], + providers: [UserService], // Missing imports, but UserService depends on DatabaseModule }) class UserModule {} @@ -235,9 +260,13 @@ Document your modules with clear descriptions: * - LoggingModule for audit trails */ @Module({ - services: [UserService, UserRepository, UserValidator], + providers: [ + UserService, + UserRepository, + UserValidator, + { token: USER_CONFIG, useValue: userConfig }, + ], imports: [DatabaseModule, LoggingModule], - providers: [{ token: USER_CONFIG, useValue: userConfig }], }) class UserModule {} ``` @@ -250,7 +279,7 @@ You can achieve environment-specific configuration by creating separate modules ```typescript @Module({ - services: [LoggerService], + providers: [LoggerService], providers: [ { token: LOG_CONFIG, useValue: { level: 'debug', format: 'detailed' } }, ], @@ -258,7 +287,7 @@ You can achieve environment-specific configuration by creating separate modules class DevelopmentLoggingModule {} @Module({ - services: [LoggerService], + providers: [LoggerService], providers: [ { token: LOG_CONFIG, useValue: { level: 'info', format: 'json' } }, ], @@ -266,7 +295,7 @@ class DevelopmentLoggingModule {} class ProductionLoggingModule {} @Module({ - services: [LoggerService], + providers: [LoggerService], providers: [ { token: LOG_CONFIG, useValue: { level: 'error', format: 'minimal' } }, ], @@ -301,19 +330,19 @@ interface EmailConfig { } @Module({ - services: [EmailService], + providers: [EmailService], providers: [{ token: EMAIL_CONFIG, useValue: { provider: 'sendgrid' } }], }) class SendGridEmailModule {} @Module({ - services: [EmailService], + providers: [EmailService], providers: [{ token: EMAIL_CONFIG, useValue: { provider: 'mailgun' } }], }) class MailgunEmailModule {} @Module({ - services: [EmailService], + providers: [EmailService], providers: [{ token: EMAIL_CONFIG, useValue: { provider: 'smtp' } }], }) class SmtpEmailModule {} @@ -337,7 +366,7 @@ Create composite modules that import other modules: ```typescript @Module({ - services: [DatabaseService], + providers: [DatabaseService], providers: [ { token: DATABASE_CONFIG, @@ -348,7 +377,7 @@ Create composite modules that import other modules: class DatabaseModule {} @Module({ - services: [EmailService], + providers: [EmailService], providers: [ { token: EMAIL_CONFIG, @@ -359,7 +388,7 @@ class DatabaseModule {} class EmailModule {} @Module({ - services: [LoggerService], + providers: [LoggerService], providers: [ { token: LOG_CONFIG, @@ -372,7 +401,7 @@ class EmailModule {} class LoggingModule {} @Module({ - services: [AppService], + providers: [AppService], imports: [DatabaseModule, EmailModule, LoggingModule], }) class AppModule {} @@ -403,7 +432,7 @@ function createDatabaseModule(config: DatabaseConfig) { validateDatabaseConfig(config); @Module({ - services: [DatabaseService], + providers: [DatabaseService], providers: [{ token: DATABASE_CONFIG, useValue: config }], }) class ValidatedDatabaseModule {} @@ -431,7 +460,7 @@ describe('DatabaseModule', () => { const container = new Nexus(); @Module({ - services: [DatabaseService], + providers: [DatabaseService], providers: [ { token: DATABASE_CONFIG, diff --git a/docs/docs/modules/modules.md b/docs/docs/modules/modules.md index 12c850e..aba4d87 100644 --- a/docs/docs/modules/modules.md +++ b/docs/docs/modules/modules.md @@ -38,7 +38,7 @@ class UserService implements IUserService { // Create the module @Module({ - services: [UserService], + providers: [UserService], providers: [{ token: DATABASE, useClass: PostgresDatabase }], }) export class UserModule {} diff --git a/docs/docs/performance.md b/docs/docs/performance.md index 8c9e57d..e3db994 100644 --- a/docs/docs/performance.md +++ b/docs/docs/performance.md @@ -306,14 +306,12 @@ if (process.env.ENABLE_CACHING === 'true') { // Split by feature modules // user-module.js export const UserModule = { - services: [UserService, UserRepository], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [UserService, UserRepository], }; // email-module.js export const EmailModule = { - services: [EmailService], - providers: [{ token: EMAIL_PROVIDER, useClass: SendGridProvider }], + providers: [EmailService], }; ``` @@ -507,14 +505,12 @@ class UserService { ```typescript // Split modules by feature to enable tree shaking @Module({ - services: [UserService, UserRepository], - providers: [{ token: DATABASE, useClass: PostgresDatabase }], + providers: [UserService, UserRepository], }) class UserModule {} @Module({ - services: [EmailService], - providers: [{ token: EMAIL_PROVIDER, useClass: SendGridProvider }], + providers: [EmailService], }) class EmailModule {} ``` diff --git a/docs/docs/providers-and-services.md b/docs/docs/providers-and-services.md index 5a29090..11d73e9 100644 --- a/docs/docs/providers-and-services.md +++ b/docs/docs/providers-and-services.md @@ -244,7 +244,27 @@ nexus.set(DATABASE_CONNECTION, { ## Provider Registration Patterns -### Direct Registration +### Conditional Registration + +You can register providers conditionally based on environment, feature flags, or runtime logic: + +```typescript +if (process.env.NODE_ENV === 'production') { + nexus.set(LOGGER, { useClass: ProductionLogger }); +} else { + nexus.set(LOGGER, { useClass: ConsoleLogger }); +} + +if (process.env.EMAIL_PROVIDER === 'sendgrid') { + nexus.set(EMAIL_SERVICE, { useClass: SendGridEmailService }); +} else { + nexus.set(EMAIL_SERVICE, { useClass: ConsoleEmailService }); +} +``` + +### Module Registration Patterns + +#### Direct Registration ```typescript // Register services directly @@ -253,41 +273,36 @@ nexus.set(DATABASE, { useClass: PostgresDatabase }); nexus.set(LOGGER, { useClass: ConsoleLogger }); ``` -### Module Registration +#### Module Registration ```typescript // Register services through modules const UserModule = { - services: [UserService, UserRepository], - providers: [ - { token: DATABASE, useClass: PostgresDatabase }, - { token: LOGGER, useClass: ConsoleLogger }, - ], + providers: [UserService, UserRepository], }; nexus.set(UserModule); ``` -### Conditional Registration +#### Environment-Based Module Registration ```typescript -// Register based on environment +// Register different modules based on environment if (process.env.NODE_ENV === 'production') { - nexus.set(LOGGER, { useClass: StructuredLogger }); - nexus.set(DATABASE, { useClass: PostgresDatabase }); + nexus.set(ProductionModule); } else { - nexus.set(LOGGER, { useClass: ConsoleLogger }); - nexus.set(DATABASE, { useClass: InMemoryDatabase }); -} - -// Register based on configuration -if (process.env.EMAIL_PROVIDER === 'sendgrid') { - nexus.set(EMAIL_SERVICE, { useClass: SendGridEmailService }); -} else { - nexus.set(EMAIL_SERVICE, { useClass: ConsoleEmailService }); + nexus.set(DevelopmentModule); } ``` +### Best Practices + +- Use class constructors for services in the providers array. +- Use the object form for advanced providers (custom tokens, useValue, useFactory, etc.). +- Prefer module registration for grouping related providers. +- Use conditional registration for environment-specific or feature-flagged services. +- Document your provider registration patterns for maintainability. + ## Summary The `deps` parameter essentially tells NexusDI: "When you call this factory function, inject these dependencies as arguments in this order." @@ -302,6 +317,8 @@ For lifetimes and scoping, see [Scoped & Transient Lifetimes](advanced/scoped-an ## Next Steps -- **[Module Basics](./modules/module-basics.md)** - How to organize services into modules +- **[Module Basics](./module-basics.md)** - Learn the fundamentals of modules +- **[Module Patterns](./module-patterns.md)** - Explore common module patterns +- **[Dynamic Modules](./dynamic-modules.md)** - Runtime configuration and validation - **[Testing](./testing.md)** - How to test services and providers - **[Advanced](advanced/advanced.md)** - Advanced provider patterns and techniques diff --git a/docs/docs/roadmap.md b/docs/docs/roadmap.md index 5088f69..f979934 100644 --- a/docs/docs/roadmap.md +++ b/docs/docs/roadmap.md @@ -9,16 +9,16 @@ This is a living document outlining planned and possible features for NexusDI. E ## Summary Table -| Feature | Market Impact | Performance Impact | Notes | -| ------------------------------------------- | :-----------: | :----------------: | ------------------------------------------------------------------ | -| Lifetime Decorators | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | Ergonomics, parity with top DI libraries | -| Automatic Disposal | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | Essential for resource management | -| Circular Dependency Fix | β˜…β˜…β˜…β˜…β˜† | β˜…β˜…β˜†β˜†β˜† | Lazy proxies, matches Angular/NestJS/TypeDI | -| Interceptors/Middleware | β˜…β˜…β˜…β˜…β˜† | β˜…β˜…β˜†β˜†β˜† | AOP, cross-cutting concerns, enterprise appeal | -| Graph Visualization | β˜…β˜…β˜…β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | Dev tool only, great for onboarding/debugging | -| Plugin/Extension System | β˜…β˜…β˜…β˜…β˜… | β˜†β˜†β˜†β˜†β˜† | Ecosystem driver, enables integrations and community growth | -| Native Decorator Metadata (Symbol.metadata) | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | Standards-based, enables removal of reflect-metadata, future-proof | -| Benchmarking Suite & Nx Plugin | β˜…β˜…β˜…β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | Enables transparent, reproducible performance comparisons | +| Feature | Market Impact | Performance Impact | Progress | Notes | +| ------------------------------------------- | :-----------: | :----------------: | :------: | ------------------------------------------------------------------ | +| Lifetime Decorators | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | Planned | Ergonomics, parity with top DI libraries | +| Automatic Disposal | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | Planned | Essential for resource management | +| Circular Dependency Fix | β˜…β˜…β˜…β˜…β˜† | β˜…β˜…β˜†β˜†β˜† | Planned | Lazy proxies, matches Angular/NestJS/TypeDI | +| Interceptors/Middleware | β˜…β˜…β˜…β˜…β˜† | β˜…β˜…β˜†β˜†β˜† | Planned | AOP, cross-cutting concerns, enterprise appeal | +| Graph Visualization | β˜…β˜…β˜…β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | Planned | Dev tool only, great for onboarding/debugging | +| Plugin/Extension System | β˜…β˜…β˜…β˜…β˜… | β˜†β˜†β˜†β˜†β˜† | Planned | Ecosystem driver, enables integrations and community growth | +| Native Decorator Metadata (Symbol.metadata) | β˜…β˜…β˜…β˜…β˜† | β˜…β˜†β˜†β˜†β˜† | βœ… Done | Standards-based, enables removal of reflect-metadata, future-proof | +| Benchmarking Suite & Nx Plugin | β˜…β˜…β˜…β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | Planned | Enables transparent, reproducible performance comparisons | --- @@ -262,6 +262,8 @@ container.registerModule( ## Native Decorator Metadata (Symbol.metadata) +> **βœ… Completed** + **Description:** Migrate from the legacy `reflect-metadata` library to the new standards-based decorator metadata protocol using `Symbol.metadata`, as supported in TypeScript 5.2+ and the upcoming ECMAScript standard. - **Market Impact:** High (future-proof, reduces dependencies, aligns with ECMAScript) @@ -385,14 +387,13 @@ class DatabaseService { ```typescript // Dynamic module configuration container.set(DatabaseModule, { - services: [DatabaseService, ConnectionPool], - providers: [{ token: DATABASE_CONFIG, useValue: { host: 'localhost' } }], + providers: [DatabaseService, ConnectionPool], }); // Framework integrations container.set(TypeOrmModule, { - services: [TypeOrmService], providers: [ + TypeOrmService, { token: TYPEORM_CONFIG, useValue: { diff --git a/docs/docs/testing.md b/docs/docs/testing.md index c79657c..57846e6 100644 --- a/docs/docs/testing.md +++ b/docs/docs/testing.md @@ -130,7 +130,7 @@ class UserService implements IUserService { } @Module({ - services: [UserService], + providers: [UserService], providers: [ { token: DATABASE, useClass: InMemoryDatabase }, { token: LOGGER, useClass: ConsoleLogger }, @@ -167,7 +167,7 @@ describe('UserModule', () => { ```typescript // Create a test module with mocked dependencies @Module({ - services: [UserService], + providers: [UserService], providers: [ { token: DATABASE, useValue: mockDatabase }, { token: LOGGER, useValue: mockLogger }, @@ -222,7 +222,7 @@ interface DatabaseConfig { const DATABASE_CONFIG = new Token('DATABASE_CONFIG'); @Module({ - services: [DatabaseService], + providers: [DatabaseService], providers: [{ token: DATABASE_CONFIG, useValue: {} }], }) class DatabaseModule extends DynamicModule { @@ -273,20 +273,20 @@ describe('DatabaseModule', () => { ```typescript @Module({ - services: [UserService], + providers: [UserService], providers: [{ token: DATABASE, useClass: PostgresDatabase }], }) class UserModule {} @Module({ - services: [EmailService], + providers: [EmailService], providers: [{ token: EMAIL_CONFIG, useValue: emailConfig }], }) class EmailModule {} @Module({ imports: [UserModule, EmailModule], - services: [AppService], + providers: [AppService], }) class AppModule {} @@ -461,7 +461,7 @@ describe('Module Configuration', () => { ```typescript // Create test-specific modules for different scenarios @Module({ - services: [UserService], + providers: [UserService], providers: [ { token: DATABASE, useValue: inMemoryDatabase }, { token: LOGGER, useValue: silentLogger }, From 1899fd3ad409af41e9871137c4ba3df0b7d4c0a5 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Sat, 28 Jun 2025 01:16:16 +0200 Subject: [PATCH 10/10] docs: blog post about native decorators --- ...06-26-native-decorators-simpler-modules.md | 127 ++++++++++++++++++ docs/blog/tags.yml | 20 +++ 2 files changed, 147 insertions(+) create mode 100644 docs/blog/2025-06-26-native-decorators-simpler-modules.md diff --git a/docs/blog/2025-06-26-native-decorators-simpler-modules.md b/docs/blog/2025-06-26-native-decorators-simpler-modules.md new file mode 100644 index 0000000..f414014 --- /dev/null +++ b/docs/blog/2025-06-26-native-decorators-simpler-modules.md @@ -0,0 +1,127 @@ +--- +title: 'The jump to lightspeed' +authors: [evanion] +tags: [release, decorators, typescript, modules, breaking-changes] +description: "NexusDI now uses native decorator metadata, drops reflect-metadata, and unifies module configuration. Here's what's new, why it matters, and how to upgrade." +--- + +# πŸš€ Native Decorators, Simpler Modules, and a Future-Proof NexusDI + +NexusDI just made the jump to lightspeed! With our latest release, you can say goodbye to `reflect-metadata`, enjoy a cleaner module API, and rest easy knowing your DI setup is ready for the next era of TypeScript and JavaScript. Let's take a tour of what's new, why it matters, and how to upgrade. + + + +## ⚑ Native Decorator Metadata (No More reflect-metadata!) + +NexusDI now uses the new [ECMAScript decorator metadata standard](https://github.com/tc39/proposal-decorator-metadata) via `Symbol.metadata`, as supported in TypeScript 5.2+ and the upcoming JavaScript standard. + +- **No more `reflect-metadata`**: You don't need to install or import itβ€”ever (unless your other dependencies require it). +- **Cleaner tsconfig**: No more `emitDecoratorMetadata`. Just enable `experimentalDecorators` and (optionally) `useDefineForClassFields` (the default in TS 5.2+). +- **Automatic polyfill**: NexusDI includes a tiny, built-in polyfill for `Symbol.metadata` if your runtime doesn't support it yet. No extra setup required. +- **Future-proof**: Your code is ready for the next generation of TypeScript and JavaScript. + +> "It's like switching from a hyperdrive that needs constant repairs to one that just works." + +### Example + +```typescript +import { Service, Inject, Nexus, Token } from '@nexusdi/core'; + +const USER_SERVICE = new Token('UserService'); + +@Service() +class UserService { + getUsers() { + return ['Alice', 'Bob', 'Charlie']; + } +} + +const container = new Nexus(); +container.set(USER_SERVICE, UserService); + +const userService = container.get(USER_SERVICE); +console.log(userService.getUsers()); // ['Alice', 'Bob', 'Charlie'] +``` + +> **Note:** NexusDI v0.3+ uses native decorator metadata (TypeScript 5.2+). You do not need to install or import `reflect-metadata`, and you do not need `emitDecoratorMetadata` in your tsconfig. Only `experimentalDecorators` and `useDefineForClassFields` (default in TypeScript 5.2+) are required. + +--- + +## πŸ“¦ Unified Module API: `services` and `providers` Merged + +Modules are now simpler and less confusing! Instead of juggling both `services` and `providers` arrays, you now use a single `providers` array for everything: + +**Before:** + +```typescript +@Module({ + services: [UserService], + providers: [{ token: USER_REPO, useClass: UserRepo }], +}) +export class UserModule {} +``` + +**Now:** + +```typescript +@Module({ + providers: [UserService, { token: USER_REPO, useClass: UserRepo }], +}) +export class UserModule {} +``` + +- **Less boilerplate**: One array, all your dependencies. +- **Clearer intent**: No more guessing where things go. + +--- + +## 🎯 Why This Matters + +- **Smaller bundles**: No more `reflect-metadata` means less code in your app. +- **Faster startup**: Native metadata is faster and more reliable. +- **Modern and future-proof**: Aligns with the latest TypeScript and ECMAScript standards. +- **Easier migration**: The new module API is simpler and more intuitive. + +--- + +## πŸ”§ How to Upgrade + +1. **Update TypeScript**: Make sure you're on TypeScript 5.2 or later. +2. **Update your tsconfig**: + - Cleanup `emitDecoratorMetadata`, if you don't need it for anything else + - Ensure `experimentalDecorators` is enabled + - (Optional) `useDefineForClassFields` should be enabled (default in TS 5.2+) +3. **Remove all imports of `reflect-metadata`**. +4. **Update your modules**: Merge `services` and `providers` into a single `providers` array. +5. **Enjoy a cleaner, faster, and more modern DI experience!** + +--- + +## πŸ§‘β€πŸ’» Other Notable Improvements + +- **Dynamic Modules**: More flexible module registration and configuration. (was ghost launched in 0.2) +- **Better Type Safety**: Improved generics and overloads for decorators and containers. +- **Performance**: Optimized for tree-shaking and minimal runtime overhead. +- **Polyfill Included**: No manual setup for `Symbol.metadata`β€”it just works. + +--- + +## πŸ›£οΈ Next Steps + +- [Getting Started Guide](../docs/getting-started) +- [Advanced Providers & Factories](../docs/advanced/advanced-providers-and-factories) +- [Best Practices](../docs/best-practices) +- [Roadmap](../docs/roadmap) + +--- + +## ✨ Lets boldly go where few have gone before! + +This release is a big step toward a simpler, more powerful, and future-ready dependency injection experience for TypeScript and JavaScript developers. We can't wait to see what you build with NexusDI! + +Have questions, feedback, or want to contribute? [Check out our docs](https://nexus.js.org/), [join the discussion](https://github.com/NexusDI/core/discussions), or [open an issue](https://github.com/NexusDI/core/issues). + +--- + +_Want to help improve NexusDI?_ +Read our [contribution guidelines](/docs/contributing) and join the journey! πŸš€ diff --git a/docs/blog/tags.yml b/docs/blog/tags.yml index dab403c..34c1168 100644 --- a/docs/blog/tags.yml +++ b/docs/blog/tags.yml @@ -33,6 +33,26 @@ dependency-injection: permalink: /dependency-injection description: Articles about DI patterns and libraries +decorators: + label: Decorators + permalink: /decorators + description: Articles about decorators + +typescript: + label: TypeScript + permalink: /typescript + description: Articles about TypeScript + +modules: + label: Modules + permalink: /modules + description: Articles about modules + +breaking-changes: + label: Breaking Changes + permalink: /breaking-changes + description: Articles about breaking changes + v0.1.0: label: v0.1.0 permalink: /v0.1.0