--- url: /guide/package/di.md description: Tutorials and references for the @wroud/di dependency injection library. --- # @wroud/di Guides Welcome to the @wroud/di library guides! Here you'll find comprehensive documentation and tutorials to help you get started with our dependency injection (DI) library. ## Getting Started * **[Introduction](getting-started/introduction)**: Learn about the key features and benefits of using @wroud/di in your projects. * **[Installation](getting-started/installation)**: Step-by-step guide to installing @wroud/di in your environment. * **[Why DI?](getting-started/why-use-dependency-injection)**: Understand the benefits of DI and when to use it, with practical examples. ## Core Concepts * **[Service Container](core-concepts/service-container)**: Understand how to register and resolve services using the ServiceContainerBuilder and IServiceProvider. * **[Service Lifetimes](core-concepts/service-lifetimes)**: Learn about different service lifetimes (singleton, transient, scoped) and how to use them effectively. * **[Dependency Injection](core-concepts/dependency-injection)**: Dive into the principles of dependency injection and how @wroud/di implements them. ## Advanced Features * **[Manual Service Registration](advanced-features/manual-service-registration)**: Learn how to manually register services and their dependencies without using decorators. * **[Factory Services](advanced-features/factory-services)**: Learn how to create and inject factory services for dynamic service creation. * **[Service Disposal](advanced-features/service-disposal)**: Understand how to manage and dispose of services properly. ## Scaling Your Application * **[Introduction](scaling/introduction)**: Introduction to scaling your application using @wroud/di and the ModuleRegistry. * **[Integration](scaling/integration)**: Step-by-step guide to integrating the ModuleRegistry into your existing application for better scalability. ## API Reference * **[API Documentation](/packages/di/api)**: Detailed reference for all classes, methods, and functions provided by @wroud/di. --- --- url: /packages/di/api.md --- # API This article provides an overview of the main API for the `@wroud/di` package. It explains the key classes and functions available for managing dependency injection. ## ServiceContainerBuilder The `ServiceContainerBuilder` class is used to register services and build the service provider. ### Methods * **`addSingleton(service: SingleServiceImplementation): this`** * Registers a singleton service to itself. * **`addSingleton(service: SingleServiceType, factory: IServiceFactory): this`** * Registers a singleton service with a factory implementation. * **`addSingleton(service: SingleServiceType, constructor: IServiceConstructor): this`** * Registers a singleton service with a constructor implementation. * **`addSingleton(service: SingleServiceType, implementation: T): this`** * Registers a singleton service with an instance as implementation. * **`addSingleton(service: SingleServiceType, resolver: IServiceImplementationResolver): this`** * Registers a singleton service with a service resolver as implementation. * **`addTransient(service: SingleServiceImplementation): this`** * Registers a transient service. * **`addTransient(service: SingleServiceType, factory: IServiceFactory): this`** * Registers a transient service with a factory implementation. * **`addTransient(service: SingleServiceType, constructor: IServiceConstructor): this`** * Registers a transient service with a constructor implementation. * **`addTransient(service: SingleServiceType, resolver: IServiceImplementationResolver): this`** * Registers a transient service with an service resolver as implementation. * **`addScoped(service: SingleServiceImplementation): this`** * Registers a scoped service. * **`addScoped(service: SingleServiceType, factory: IServiceFactory): this`** * Registers a scoped service with a factory implementation. * **`addScoped(service: SingleServiceType, constructor: IServiceConstructor): this`** * Registers a scoped service with a constructor implementation. * **`addScoped(service: SingleServiceType, resolver: IServiceImplementationResolver): this`** * Registers a scoped service with an service resolver as implementation. * **`build(): IServiceProvider`** * Builds and returns the service provider. ### Example ```ts import { ServiceContainerBuilder } from "@wroud/di"; import Logger from "./Logger"; import ILoggerService from "./ILoggerService"; const builder = new ServiceContainerBuilder(); builder.addSingleton(Logger); builder.addSingleton(ILoggerService, Logger); builder.addSingleton(ILoggerService, () => new Logger()); const provider = builder.build(); ``` ## IServiceProvider The `IServiceProvider` interface is used to resolve services. ### Methods * **`getService(service: ServiceType): T`** * Resolves and returns an instance of the requested service. * **`getServices(service: ServiceType): T[]`** * Resolves and returns all instances of the requested service. * **`getServiceAsync(service: ServiceType): Promise`** * Resolves and returns an instance of the requested service asynchronously. * **`getServicesAsync(service: ServiceType): Promise`** * Resolves and returns all instances of the requested service asynchronously. * **`createScope(): IAsyncServiceScope`** * Creates and returns a new scope with `IServiceProvider` and `Symbol.dispose` function. * **`createAsyncScope(): IServiceScope`** * Creates and returns a new scope with `IServiceProvider` and `Symbol.asyncDispose` function. * **`[Symbol.dispose](): void`** * Disposes of the `IServiceProvider` and any services that require disposal. * **`[Symbol.asyncDispose](): Promise`** * Disposes of the `IServiceProvider` and any services that require disposal. ### Example ```ts import Logger from "./Logger"; const logger = provider.getService(Logger); logger.log("Hello world!"); const loggers = provider.getServices(Logger); loggers.forEach((logger) => logger.log("Hello from multiple loggers!")); const scope = provider.createScope(); const scopedLogger = scope.serviceProvider.getService(Logger); scopedLogger.log("Scoped Hello world!"); provider.dispose(); ``` ## lazy The `lazy` function allows you to define services that are loaded asynchronously, enabling lazy-loading within your application. This function wraps the dynamic `import` statement, ensuring that the service is only loaded when first requested. ### Usage To use the `lazy` function, wrap your service's dynamic `import` statement within `lazy`, and register the service as usual. ### Example ```ts import { lazy, ServiceContainerBuilder } from "@wroud/di"; import { IAdministration } from "./Administration/IAdministration"; const builder = new ServiceContainerBuilder(); builder.addSingleton( IAdministration, lazy(() => import("./Administration/Administration").then((m) => m.Administration), ), ); ``` In this example, the `IAdministration` service is registered to be loaded asynchronously. The service will only be resolved when accessed using the `getServiceAsync` or `getServicesAsync` methods of `IServiceProvider`. ## ServiceRegistry The `ServiceRegistry` class allows registering service metadata such as dependencies. ### Methods * **`register(service: Class, metadata: IServiceMetadata)`** * Registers a service with its metadata. ### Example ```ts import { all, optional, ServiceRegistry } from "@wroud/di"; import Logger from "./Logger"; import Formatter from "./Formatter"; import LoggerNode from "./LoggerNode"; import LoggerBrowser from "./LoggerBrowser"; ServiceRegistry.register(Logger, { name: "Logger", dependencies: [all(Formatter), LoggerNode, optional(LoggerBrowser)], }); ``` * Injects an array of `Formatter` implementations as the first argument of a constructor, `LoggerNode` implementation as the second argument and implementation resolver of `LoggerBrowser` as a third argument. ## createService The `createService` function creates a token that can be used to resolve services. ### Example ```ts const ILoggerService = createService("ILoggerService"); ``` ## injectable The `injectable` decorator marks a class as injectable and registers its dependencies. ### Overloads * `@injectable()` * `@injectable(() => [Service])` * Injects the `Service` implementation as the first argument of a constructor. * `@injectable(() => [Service, [Service2]])` * Injects the `Service` implementation as the first argument of a constructor and an array of `Service2` implementations as the second argument of a constructor. * `@injectable(({ single, all, optional }) => [single(Service), all(Service2), optional(Service3)])` * Injects the `Service` implementation as the first argument of a constructor, an array of `Service2` implementations as the second argument of a constructor and the `Service3` implementation resolver as the third argument of a constructor. ### Example ```ts import { injectable, type IOptionalService } from "@wroud/di"; @injectable() class Logger { log(message: string) { console.log(message); } } @injectable(() => [Logger]) class Service { constructor(private readonly logger: Logger) {} action() { this.logger.log("Action executed"); } } @injectable(() => [Logger, [Service]]) class AnotherService { constructor( private readonly logger: Logger, private readonly services: Service[], ) {} anotherAction() { this.services.forEach((service) => service.action()); this.logger.log("Another action executed"); } } @injectable(({ all, optional }) => [ Logger, all(Service), optional(NodeLogger), ]) class AnotherService { constructor( private readonly logger: Logger, private readonly services: Service[], private readonly nodeLogger: IOptionalService, ) {} anotherAction() { this.services.forEach((service) => service.action()); this.logger.log("Another action executed"); } yetOneAnotherAction() { const nodeLogger = this.nodeLogger.resolve(); nodeLogger.log("Yet One Another action executed"); } } ``` *** This overview provides a quick reference to the main classes and functions in the `@wroud/di` package. For more detailed information, refer to the full documentation. --- --- url: /packages/di/integrations/react/api.md --- # API This article provides an overview of the main API for the `@wroud/di-react` package. It explains the key components and hooks available for managing dependency injection in React applications. ## ServiceProvider The `ServiceProvider` component is used to supply a service provider context to child components, enabling them to resolve services. ### Props * **`provider: IServiceProvider`** * The service provider instance used to resolve services within the component tree. ### Example ```tsx import { ServiceProvider } from "@wroud/di-react"; import { Main } from "./Main.js"; import { getServiceProvider } from "./getServiceProvider.js"; export function App() { return (
); } ``` ## useService The `useService` hook is used to resolve a single service instance. If the service is lazy-loaded, React's Suspense mechanism will handle its resolution. ### Arguments * **`type: ServiceType`** * The service type to resolve. ### Example ```tsx import { useService } from "@wroud/di-react"; import Logger from "./Logger.js"; function SomeComponent() { const logger = useService(Logger); function handleClick() { logger.log("Hello World!"); } return ( ); } ``` ## useServices The `useServices` hook is used to resolve multiple instances of a service. Like `useService`, it utilizes React's Suspense mechanism to handle lazy-loaded services. ### Arguments * **`type: ServiceType`** * The service type to resolve. ### Example ```tsx import { useServices } from "@wroud/di-react"; import Logger from "./Logger.js"; function SomeComponent() { const loggers = useServices(Logger); function handleClick() { loggers.forEach((logger) => { logger.log("Hello World!"); }); } return ( ); } ``` ## useServiceCreateAsyncScope The `useServiceCreateAsyncScope` hook creates an asynchronous service scope. It requires a `ServiceProvider` to be present in the parent components to function properly. ### Example ```tsx import { useServiceCreateAsyncScope, ServiceProvider } from "@wroud/di-react"; function SomeComponent() { const scopeServiceProvider = useServiceCreateAsyncScope(); return ...; } ``` ## useServiceCreateScope The `useServiceCreateScope` hook creates a new service scope. Similar to `useServiceCreateAsyncScope`, it requires a `ServiceProvider` in the parent components. ### Example ```tsx import { useServiceCreateScope, ServiceProvider } from "@wroud/di-react"; function SomeComponent() { const scopeServiceProvider = useServiceCreateScope(); return ...; } ``` *** This overview provides a quick reference to the key components and hooks in the `@wroud/di-react` package. For more detailed information, please refer to the full documentation. --- --- url: /packages/react-split-view/api.md --- # API ## useSplitView ```ts function useSplitView(options?: { sticky?: number; }): { viewProps: React.HTMLAttributes; sashProps: React.HTMLAttributes; }; ``` `useSplitView` is part of a pure ESM package and provides typed helpers for managing split panes. ### Options | Option | Type | Description | | -------- | ------ | ---------------------------------------------------------- | | `sticky` | number | Distance in pixels from the edge where the sash will snap. | ### Return Value * `viewProps` – props to spread on the resizable element. * `sashProps` – props to spread on the divider element for drag handling. --- --- url: /packages/react-tree/api.md --- # API This page lists the main exports from `@wroud/react-tree`. See the source code for detailed comments (e.g., the `ITree` interface). ## Components * **`Tree`** – top-level component that provides contexts and renders nodes. * **`Node`** – renders a single tree node. * **`NodeChildren`** – virtualized renderer for a node's children. * **`TreeNodeControl`**, **`TreeNodeExpand`**, **`TreeNodeIcon`**, **`TreeNodeName`** – building blocks for custom node UIs. ## Hooks * **`useTree`** – creates a tree controller implementing `ITree`. * **`useNodeSizeCache`** – caches node heights for virtualization. * **`useTreeViewport`** – tracks the visible range of the tree. ## Contexts * **`TreeContext`**, **`TreeDataContext`**, **`NodeSizeCacheContext`**, **`TreeVirtualizationContext`**, **`TreeClassesContext`** – provide state and styling information. ## Utilities * **`treeClasses`**, **`getCombinedClasses`** – default CSS classes and merging helper. ## Types * `ITree`, `ITreeProps`, `ITreeData` * `INode`, `INodeState` * `NodeComponent`, `NodeComponentProps` * `INodeSizeCache`, `IViewPort`, `ITreeVirtualization`, `UseTreeViewportResult` * `ITreeClasses` ## Internals * `NodeControl` and `NodeRenderer` are exported for reference when implementing custom nodes. --- --- url: /guide/package/di/advanced-features/code-splitting.md --- # Code Splitting @wroud/di introduces the ability to load services asynchronously, enabling advanced features like code-splitting and lazy-loading. This feature is designed to improve the flexibility and performance of applications by allowing services to be loaded only when they are needed, rather than at startup. ### When to Use Asynchronous Loading Asynchronous service loading is most beneficial in scenarios where large portions of your application are not immediately needed at startup. Here are some guidelines to help you decide when to use it: * **Modular Applications**: If your application is divided into clearly defined modules, such as different user roles or sections, consider lazy-loading the services for these modules. * **Rarely Used Features**: Features that are rarely accessed by users, such as advanced settings or administration panels, are prime candidates for lazy loading. * **Performance Bottlenecks**: If you identify that your application’s initial load time is a performance bottleneck, consider adopting asynchronous loading for non-critical services. ### How It Works To make a service's implementation lazy, use the `lazy` function provided by `@wroud/di`. This function wraps the dynamic `import` statement, allowing the service to be loaded asynchronously. Here’s an example: ```typescript import { lazy, ServiceContainerBuilder } from "@wroud/di"; import { IAdministration } from "./Administration/IAdministration"; const builder = new ServiceContainerBuilder(); builder.addSingleton( IAdministration, lazy(() => import("./Administration/Administration").then((m) => m.Administration), ), ); ``` [Try it in the Playground](https://stackblitz.com/edit/wroud-di-code-splitting?file=src%2Fcounter.ts) In this example, the `IAdministration` service is loaded only when it is needed, using the asynchronous method `getServiceAsync` or `getServicesAsync` of `IServiceProvider`. ### Requirements Asynchronous service loading relies on the dynamic `import` feature, which is not supported in all environments. If your environment does not support dynamic `import`, you must use the standard synchronous approach to load services. ### Benefits * **Performance Optimization**: By loading services asynchronously, you can improve the initial load time of your application, particularly in large or modular applications. * **Modular Design**: Asynchronous loading encourages a modular design, allowing you to separate concerns and load only what is necessary at any given time. ## Error Handling and Validation When working with asynchronous service loading in `@wroud/di`, error handling is crucial to ensure the reliability of your application. The good news is that error handling for asynchronous services follows the same patterns as synchronous services, making it straightforward to integrate. ### Error Handling Asynchronous service loading errors are handled in the same way as errors in the synchronous approach. The key difference is that when loading services asynchronously, you’ll need to manage promises and potential issues that might arise during the dynamic `import` process. For example, if a service fails to load due to an error in the `import` statement, this error will be caught in the usual manner with a `try-catch` block or by handling the promise rejection: ```typescript try { const adminService = await serviceProvider.getServiceAsync(IAdministration); } catch (error) { console.error("Failed to load administration service:", error); } ``` This approach ensures that your application can gracefully handle failures in service loading, providing a fallback or logging the error for further analysis. ### Validation of Circular Dependencies A unique challenge with asynchronous service loading is that circular dependencies cannot be detected at the time of service registration. This is because the dependencies might not be fully resolved when you add services to the `ServiceContainerBuilder`. To address this, `@wroud/di` provides a `validate` method on the `ServiceContainerBuilder` class. This method allows you to verify that there are no circular dependencies among your services before you build the service container: ```typescript const builder = new ServiceContainerBuilder(); // Register services builder.addSingleton(IAdministration /* lazy loading setup */); // Validate circular dependencies await builder.validate(); // Build the service container const serviceProvider = builder.build(); ``` ::: warning The `validate` method is asynchronous and should be awaited. This method will load all implementations, so it is not recommended to use it in production. ::: ### Development Environment Warnings In development environments, if the `validate` method is not called before invoking the `build()` method, `@wroud/di` will emit warnings in the console about potential undetected circular dependencies. These warnings serve as a reminder to validate your service configuration to avoid runtime issues. ## Migration Guide With the introduction of asynchronous service loading in `@wroud/di`, you might wonder how this affects your existing codebase. Fortunately, migrating to take advantage of this new feature is straightforward and does not involve any breaking changes. ### No Breaking Changes The asynchronous service loading feature is fully compatible with previous versions of `@wroud/di`. This means that all your existing services, configured and loaded synchronously, will continue to work as expected. You can gradually adopt asynchronous loading in parts of your application where it makes sense, without needing to refactor your entire codebase. ### Adopting Asynchronous Service Loading To start using asynchronous service loading, you can follow these simple steps: 1. **Identify Candidates for Lazy Loading**: * Review your application to identify services that can benefit from lazy loading. These are typically services that are part of non-essential modules, large features, or sections of your application that are not immediately needed on startup. 2. **Refactor Service Registration**: * For each service that you want to load asynchronously, modify its registration in the `ServiceContainerBuilder` to use the `lazy` function with dynamic `import`. Here’s an example of how to refactor a service: ```typescript import { lazy, ServiceContainerBuilder } from "@wroud/di"; import { IAdministration } from "./Administration/IAdministration"; const builder = new ServiceContainerBuilder(); builder.addSingleton( IAdministration, lazy(() => import("./Administration/Administration").then( (m) => m.Administration, ), ), ); ``` 3. **Update Service Resolution**: * When you resolve these services in your application, use the asynchronous methods `getServiceAsync` or `getServicesAsync` provided by `IServiceProvider`. This ensures that your application waits for the service to be loaded before it is used: ```typescript const adminService = await serviceProvider.getServiceAsync(IAdministration); ``` 4. **Testing and Validation**: * After refactoring, thoroughly test your application to ensure that the services are loaded as expected. Use the `validate` method of `ServiceContainerBuilder` in your development environment to check for circular dependencies. ### Best Practices * **Gradual Adoption**: Start by lazy loading non-critical services or those that are part of large, infrequently used modules. This allows you to gradually introduce asynchronous loading without overwhelming your development process. * **Consistency in Service Resolution**: Once you decide to load a service asynchronously, ensure that all resolutions of this service use the asynchronous methods to avoid runtime errors. * **Performance Monitoring**: Keep an eye on performance, particularly the loading times of asynchronously loaded services, to ensure that lazy loading is providing the expected benefits. ## Performance Considerations Introducing asynchronous service loading in `@wroud/di` can have a significant impact on the performance of your application. While this feature offers powerful benefits like code-splitting and lazy-loading, it’s important to understand how to use it effectively to optimize performance without introducing latency or complexity. ### Initial Load Time One of the primary benefits of asynchronous service loading is the potential to reduce the initial load time of your application. By deferring the loading of services that are not immediately needed, you can decrease the amount of code that must be executed at startup. This can be particularly beneficial in large applications with multiple modules or sections. ### Example Scenario Consider an application with distinct "public" and "administration" sections. The administration section is only accessible to users with specific rights. By lazily loading the services related to the administration section, you can significantly reduce the amount of code that needs to be loaded and executed when a general user accesses the public section. ### Trade-offs and Considerations While asynchronous service loading can improve performance, it’s important to be aware of potential trade-offs: * **Load Time vs. Execution Time**: Lazy-loading services can reduce initial load time, but it may introduce slight delays when these services are eventually needed. This is because the services will be loaded asynchronously when requested, which could add latency to user interactions. * **Circular Dependencies**: As mentioned in the [Error Handling and Validation](#error-handling-and-validation) section, circular dependencies are harder to detect in an asynchronous context. This makes it essential to validate your service configuration during development. ### Monitoring and Optimization After implementing asynchronous service loading, it’s crucial to monitor the performance of your application to ensure that it’s providing the expected benefits. Use performance profiling tools to measure load times and identify any new bottlenecks introduced by lazy-loading services. If necessary, refine your lazy-loading strategy by adjusting which services are loaded asynchronously and when. --- --- url: /guide/package/di/core-concepts/dependency-injection.md --- # Dependency Injection Dependency Injection (DI) is a design pattern used to implement IoC (Inversion of Control), allowing you to develop loosely coupled code. This guide will help you understand the principles of DI and how to use @wroud/di to manage dependencies in your projects. ## What is Dependency Injection? Dependency Injection is a technique where an object receives its dependencies from an external source rather than creating them itself. This promotes decoupling and enhances testability and maintainability. ### Benefits of Dependency Injection * **Decoupling**: Reduces dependencies between components, making your code more modular and easier to manage. * **Testability**: Simplifies unit testing by allowing you to mock dependencies. * **Flexibility**: Makes it easier to switch between different implementations of a service. ## Dependency Injection in @wroud/di In @wroud/di, you use the `ServiceContainerBuilder` to register services and their dependencies. The `IServiceProvider` is then used to resolve these services. ### Registering and Resolving Services Services are registered with different lifetimes using methods like `addSingleton`, `addTransient`, and `addScoped`. #### Example: Registering and Resolving Services with Decorators ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } @injectable(() => [Logger]) class UserService { constructor(private logger: Logger) { this.logger = logger; } createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name}`); // User creation logic } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); containerBuilder.addTransient(UserService); const serviceProvider = containerBuilder.build(); const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` ## Injecting Multiple Services @wroud/di allows you to inject multiple services into a single class. ### Example: Injecting Multiple Services ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } @injectable() class ConfigService { getConfig() { return { appName: 'MyApp' }; } } @injectable(() => [Logger, ConfigService]) class UserService { constructor(private logger: Logger, private configService: ConfigService) { } createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name} in ${this.configService.getConfig().appName}`); // User creation logic } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); containerBuilder.addSingleton(ConfigService); containerBuilder.addTransient(UserService); const serviceProvider = containerBuilder.build(); const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` ## Resolving Multiple Implementations of a Service You can register and resolve multiple implementations of a service type using tokens. ### Example: Resolving All Implementations of a Service Type ```javascript import { ServiceContainerBuilder, injectable, createService } from '@wroud/di'; interface ILogger { log(message: string): void; } const ILogger = createService('ILogger'); @injectable() class ConsoleLogger implements ILogger { log(message: string) { console.log(`ConsoleLogger: ${message}`); } } @injectable() class FileLogger implements ILogger { log(message: string) { // Assume logging to a file console.log(`FileLogger: ${message}`); } } @injectable(() => [[ILogger]]) class LoggingService { constructor(private loggers: ILogger[]) {} logToAll(message: string) { this.loggers.forEach(logger => logger.log(message)); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(ILogger, ConsoleLogger); containerBuilder.addSingleton(ILogger, FileLogger); containerBuilder.addTransient(LoggingService); const serviceProvider = containerBuilder.build(); const loggingService = serviceProvider.getService(LoggingService); loggingService.logToAll('This is a test message'); ``` ## Conclusion Dependency Injection is a powerful pattern for managing dependencies in your application, promoting decoupling and enhancing testability. By using @wroud/di, you can easily register and resolve services, allowing you to build modular and maintainable applications. --- --- url: /packages/di/overview.md --- # Dependency Injection ## Overview `@wroud/di` is a powerful dependency injection (DI) library inspired by the .NET framework and implemented in pure JavaScript. It leverages modern JavaScript features, including optional decorators and explicit resource management, to provide a robust and flexible DI system suitable for a wide range of applications. It also supports legacy decorators from TypeScript, ensuring broad compatibility. ## Key Features * **Small Bundle Size**: Only 10kB (minified), ensuring minimal overhead. * **Flexible DI**: Supports multiple service injections, disposals, and various lifetimes. * **Asynchronous Service Loading**: Unlocks code-splitting and lazy-loading for optimized performance. * **Modern Decorators**: Clean and maintainable code with powerful decorators. * **Service Lifetimes**: Singleton, transient, and scoped lifetimes. * **Ease of Use**: Quick start without extensive knowledge. * **Environment Compatibility**: Works in any JavaScript environment (browser, Node.js, etc.). ## Advanced Features ### Multiple Service Injection Inject multiple services seamlessly for flexible dependency management. ### Resource Management and Disposal Efficiently manage and dispose of services, adhering to TC39 proposal for explicit resource management, ensuring predictable resource handling and proper cleanup. ### Decorator Support Optional use of TC39 proposal-decorators (stage 3) for intuitive dependency management. Also supports legacy decorators from TypeScript for broad compatibility. ## Polyfills You may need the following polyfills for full compatibility: * **Decorators**: Required for environments without native decorator support. [Learn more](https://github.com/tc39/proposal-decorators) * **WeakMap**: For environments that do not support WeakMap. * **Promise**: For environments that do not support modern Promises. * **Dispose**: For explicit resource management. [Learn more](https://github.com/tc39/proposal-explicit-resource-management) --- --- url: /packages/di/integrations/react/overview.md --- # Dependency Injection for React ## Overview `@wroud/di-react` extends the powerful `@wroud/di` library by providing seamless integration with React. It enables dependency injection (DI) in React applications through intuitive components and hooks, inspired by the .NET framework's DI system. This package is designed to simplify service management in React components while supporting modern JavaScript features, including hooks and Suspense for asynchronous service loading. ## Key Features * **React Integration**: Provides React components and hooks for integrating `@wroud/di` into React applications. * **Suspense for Lazy-Loaded Services**: Automatically leverages React Suspense to defer the resolution of asynchronous services until they are needed, optimizing performance in large applications. * **Service Scoping**: Create service scopes dynamically for better lifecycle management in React components. * **Small Bundle Size**: Lightweight package with minimal overhead, designed for React apps. * **Easy to Use**: Designed for React developers with straightforward integration patterns. * **Environment Compatibility**: Works with any React environment (browser, server-side rendering, etc.). ## Advanced Features ### Service Scoping in React Service scoping allows you to create and manage service lifetimes dynamically within React components. Use `useServiceCreateScope` or `useServiceCreateAsyncScope` to manage scoped services in a parent-child component structure. ### Asynchronous Service Resolution `@wroud/di-react` supports lazy loading of services, enhancing performance by only loading services when necessary. React's Suspense mechanism is used to handle these lazy-loaded services, deferring their resolution until the component requires them. ## Polyfills For full compatibility, you may need the following polyfills in certain environments: * **WeakMap**: For environments that do not natively support WeakMap. * **Promise**: Required for environments lacking modern Promise support. * **Dispose**: For managing explicit resource disposal. [Learn more](https://github.com/tc39/proposal-explicit-resource-management) --- --- url: /guide/package/di-tools.md description: Tools for analyzing and visualizing @wroud/di dependency graphs. --- # DI Tools ## Tool Suite Overview `@wroud/di` is not just a dependency injection library; it also comes with a suite of tools designed to help you analyze and visualize your application's dependency graph. These tools provide deeper insights into how your services are interconnected, aiding in debugging, optimization, and documentation. ## Available Tools ### [`@wroud/di-tools-analyzer`](./analyzer/introduction) `@wroud/di-tools-analyzer` is a powerful tool that integrates seamlessly with `@wroud/di`. It provides the following capabilities: * **Data Collection**: Collects detailed information about the service container in a serializable JSON format. * **Visualization**: Utilizes D3.js to visualize the collected data, making it easier to understand the relationships and dependencies between services. By using `@wroud/di-tools-analyzer`, developers can better understand their service container, identify potential performance bottlenecks, and generate visual documentation of their application's dependencies. ## Conclusion The `DI Tools` suite enhances the capabilities of the `@wroud/di` library by providing tools for analyzing and visualizing the dependency graph. These tools are essential for gaining deeper insights into your application's architecture and improving the overall development workflow. --- --- url: /guide/package/di/advanced-features/factory-services.md --- # Factory Services Factory services in @wroud/di provide a flexible way to create and configure services dynamically at runtime. This guide will show you how to set up and use factory services to meet your specific application needs. ## What are Factory Services? Factory services are functions or methods that return a new instance of a service. This is particularly useful when the creation of a service depends on runtime information or when a service needs to be configured dynamically. ## Setting Up Factory Services To set up a factory service, you define a factory function that creates the service instance. Then, you can register this factory function with the service container. ### Example: Creating a Simple Factory Service In this example, we create a factory service for a `Logger` service. ```typescript import { ServiceContainerBuilder, createService, IServiceProvider } from '@wroud/di'; interface ILogger { log(message: string): void; } const ILogger = createService('ILogger'); const builder = new ServiceContainerBuilder(); builder.addSingleton(ILogger, (serviceProvider: IServiceProvider): ILogger => { return { log(message: string) { console.log(message); }, }; }); const serviceProvider = builder.build(); const logger = serviceProvider.getService(ILogger); logger.log('This is a log message'); ``` ### Example: Using Factory Services with Dependencies Factory services can also have dependencies that need to be resolved. In this example, we create a factory service for a `UserService` that depends on a `Logger` service. ```typescript import { ServiceContainerBuilder, createService, IServiceProvider, injectable } from '@wroud/di'; interface ILogger { log(message: string): void; } const ILogger = createService('ILogger'); @injectable(() => [ILogger]) class UserService { constructor(private logger: ILogger) {} createUser(name: string) { this.logger.log(`Creating user: ${name}`); return { name }; } } const builder = new ServiceContainerBuilder(); builder.addSingleton(ILogger, (serviceProvider: IServiceProvider): ILogger => { return { log(message: string) { console.log(message); }, }; }); builder.addTransient(UserService, (serviceProvider: IServiceProvider): UserService => { const logger = serviceProvider.getService(ILogger); return new UserService(logger); }); const serviceProvider = builder.build(); const userService = serviceProvider.getService(UserService); const user = userService.createUser('John Doe'); console.log(user); ``` ## Benefits of Factory Services * **Dynamic Service Creation**: Allows for the creation of services with runtime information. * **Flexible Configuration**: Services can be configured dynamically based on the current context. * **Enhanced Dependency Management**: Factory services can leverage the full power of the dependency injection system, including resolving dependencies. ## Conclusion Factory services in @wroud/di offer a powerful mechanism for creating and configuring services dynamically. By defining factory functions and registering them with the service container, you can handle complex service creation scenarios with ease. Use factory services to enhance the flexibility and configurability of your application. --- --- url: /guide.md description: Overview of Wroud Foundation documentation and packages. --- # Guide Welcome to the Wroud Foundation guide. This documentation will help you understand and utilize the packages provided by Wroud Foundation to enhance your JavaScript development experience. You'll find comprehensive tutorials, examples, and reference materials to get the most out of our tools. ## Contents ### Available Packages Discover the packages offered by Wroud Foundation, designed to simplify and improve your development process. #### Dependency Injection (DI) Learn how to use the `@wroud/di` package to implement dependency injection in your applications. This section covers everything from basic setup to advanced usage scenarios. * [Dependency Injection (DI)](/guide/package/di/) ### DI Tools Explore the tools available for analyzing and visualizing your dependency graph. * [DI Tools](/guide/package/di-tools/) * [@wroud/di-tools-analyzer](/guide/package/di-tools/analyzer/introduction) ## Getting Started If you're new to Wroud Foundation, we recommend starting with the Dependency Injection (DI) guide. It provides a comprehensive overview of the `@wroud/di` package's capabilities and how to integrate it into your projects. --- --- url: /guide/package/di/getting-started/installation.md --- # Installation In this guide, we will walk you through the steps to install and set up @wroud/di in your JavaScript project. Follow these steps to get started with dependency injection in your application. ## Prerequisites Before you begin, ensure you have the following: * **Node.js**: Make sure you have Node.js installed. You can download it from [nodejs.org](https://nodejs.org). * **npm or yarn**: A package manager to install dependencies. npm comes with Node.js, but you can also use yarn if you prefer. ## Step 1: Install @wroud/di You can install @wroud/di using npm or yarn. For complete installation instructions, please visit the [installation page](/packages/di/install). Run one of the following commands in your project directory: ::: code-group ```sh [npm] npm install @wroud/di ``` ```sh [yarn] yarn add @wroud/di ``` ```sh [pnpm] pnpm add @wroud/di ``` ```sh [bun] bun add @wroud/di ``` ::: ## Step 2: Set Up Your Project ### TypeScript ```typescript twoslash import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } @injectable(() => [Logger]) class UserService { constructor(private logger: Logger) {} createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name}`); // User creation logic } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); containerBuilder.addTransient(UserService); const serviceProvider = containerBuilder.build(); const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` ## Conclusion You have successfully installed and set up @wroud/di in your project. You are now ready to start using dependency injection to manage your services and dependencies. Explore the rest of the guides to learn more about the core concepts and advanced features of @wroud/di. --- --- url: /packages/di/install.md --- # Installation Install with npm, or check [CDN Usage](#cdn-usage) for other options: ::: code-group ```sh [npm] npm install @wroud/di ``` ```sh [yarn] yarn add @wroud/di ``` ```sh [pnpm] pnpm add @wroud/di ``` ```sh [bun] bun add @wroud/di ``` ::: ## Usage ### Shorthands The `@wroud/di` package includes handy shorthand methods. For example, `addSingleton` makes it easy to register a singleton service. Here's a quick example: ```ts twoslash import { ServiceContainerBuilder } from '@wroud/di' const builder = new ServiceContainerBuilder(); class Logger { log(message: string) { console.log(message); } } builder.addSingleton(Logger); const serviceProvider = builder.build(); const logger = serviceProvider.getService(Logger); logger.log('Hello world!'); // -> Hello world! ``` ### CJS Usage `@wroud/di` is an ESM-only package, which keeps it small. But you can still use it in CJS by dynamically importing the ESM module in Node.js. ```ts twoslash async function main() { const { ServiceContainerBuilder } = await import('@wroud/di') const builder = new ServiceContainerBuilder(); class Logger { log(message: string) { console.log(message); } } builder.addSingleton(Logger); const serviceProvider = builder.build(); const logger = serviceProvider.getService(Logger); logger.log('Hello world!'); // -> Hello world! } ``` ### CDN Usage To use `@wroud/di` in the browser through a CDN, use [esm.run](https://esm.run) or [esm.sh](https://esm.sh). ```html theme:rose-pine ``` --- --- url: /packages/di/integrations/react/install.md --- # Installation Install with npm: ::: code-group ```sh [npm] npm install @wroud/di-react ``` ```sh [yarn] yarn add @wroud/di-react ``` ```sh [pnpm] pnpm add @wroud/di-react ``` ```sh [bun] bun add @wroud/di-react ``` ::: ## Usage ### Shorthands The `@wroud/di-react` package provides useful shorthand methods. For example, `useService` simplifies injecting a service instance directly into a React component. Here’s a quick example: ```tsx twoslash import React from "react"; import { ServiceContainerBuilder } from "@wroud/di"; import { ServiceProvider, useService } from "@wroud/di-react"; const builder = new ServiceContainerBuilder(); class Logger { log(message: string) { console.log(message); } } builder.addSingleton(Logger); const serviceProvider = builder.build(); function App() { return ( ); } function Log() { const logger = useService(Logger); logger.log("Hello world!"); // -> Hello world! return <>Check the console output.; } ``` --- --- url: /packages/react-split-view/install.md --- # Installation Install with your favorite package manager, or check [CDN Usage](#cdn-usage) for other options: ::: code-group ```sh [npm] npm install @wroud/react-split-view ``` ```sh [yarn] yarn add @wroud/react-split-view ``` ```sh [pnpm] pnpm add @wroud/react-split-view ``` ```sh [bun] bun add @wroud/react-split-view ``` ::: ### CJS Usage `@wroud/react-split-view` is an ESM-only package. To use it in a CommonJS environment, dynamically import the module: ```ts async function main() { const { useSplitView } = await import("@wroud/react-split-view"); // ... } ``` ### CDN Usage For browser usage without bundling, load the module from [esm.sh](https://esm.sh) or [esm.run](https://esm.run): ```html ``` --- --- url: /packages/react-tree/install.md --- # Installation Install with your favorite package manager, or check [CDN Usage](#cdn-usage) for other options: ::: code-group ```sh [npm] npm install @wroud/react-tree ``` ```sh [yarn] yarn add @wroud/react-tree ``` ```sh [pnpm] pnpm add @wroud/react-tree ``` ```sh [bun] bun add @wroud/react-tree ``` ::: ### CJS Usage `@wroud/react-tree` is ESM-only. To use it from CommonJS, dynamically import the module: ```ts async function main() { const { Tree } = await import("@wroud/react-tree"); } ``` ### CDN Usage For quick demos in the browser, load the module from [esm.sh](https://esm.sh) or [esm.run](https://esm.run): ```html ``` --- --- url: /packages/vite-plugin-tsc/install.md --- # Installation Install with your package manager: ::: code-group ```sh [npm] npm install @wroud/vite-plugin-tsc ``` ```sh [yarn] yarn add @wroud/vite-plugin-tsc ``` ```sh [pnpm] pnpm add @wroud/vite-plugin-tsc ``` ```sh [bun] bun add @wroud/vite-plugin-tsc ``` ::: --- --- url: /packages/di/integrations.md --- # Integrations `@wroud/di` is a flexible dependency injection (DI) system designed to be compatible with a variety of JavaScript environments. Currently, the following integration is available: ## React Integration ([`@wroud/di-react`](./react/overview.md)) The `@wroud/di-react` package integrates the `@wroud/di` library with React, allowing developers to inject services directly into React components. It offers hooks such as `useService` for easy service resolution and supports advanced features like lazy loading through React Suspense. This integration simplifies managing dependencies in React applications while maintaining a small bundle size and high performance. --- --- url: /guide/package/di/getting-started/introduction.md --- # Introduction to @wroud/di Welcome to the introduction guide for @wroud/di, a lightweight and flexible dependency injection library for JavaScript. Inspired by the .NET framework, @wroud/di aims to bring the power of dependency injection to JavaScript projects, making it easier to manage dependencies and build modular, testable applications. ## What is Dependency Injection? Dependency Injection (DI) is a design pattern that helps to decouple the creation of objects from their usage. It allows you to inject dependencies into a class or function, promoting loose coupling and enhancing testability. ## Key Features @wroud/di offers a range of features to help you implement dependency injection effectively: * **Service Registration and Resolution**: Easily register services and resolve them using the `ServiceContainerBuilder` and `IServiceProvider`. * **Service Lifetimes**: Support for various service lifetimes, including singleton, transient, and scoped. * **Decorators**: Use decorators to register services and their dependencies. * **Factory Services**: Create and inject factory services for dynamic service creation. * **Service Disposal**: Manage and dispose of services properly to avoid memory leaks. ## Benefits of Using @wroud/di * **Modular Architecture**: Promote a modular architecture by decoupling components. * **Testability**: Enhance testability by injecting mock dependencies. * **Maintainability**: Improve maintainability by managing dependencies centrally. * **Flexibility**: Easily configure and manage different service lifetimes and dependencies. ## Getting Started To get started with @wroud/di, follow the [installation guide](installation) to set up the library in your project. Once installed, you can explore the core concepts and advanced features to harness the full potential of dependency injection in your application. ## Example Usage Here is a simple example to demonstrate how @wroud/di can be used in a project: ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; // Define a service @injectable() class Logger { log(message: string) { console.log(message); } } // Define another service that depends on Logger @injectable(() => [Logger]) class UserService { constructor(private logger: Logger) { this.logger = logger; } createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name}`); // User creation logic } } // Build the service container const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); containerBuilder.addTransient(UserService); const serviceProvider = containerBuilder.build(); // Resolve and use the UserService const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` In this example, the `UserService` depends on the `Logger` service. @wroud/di manages the dependency injection, ensuring that `UserService` receives an instance of `Logger`. ## Conclusion @wroud/di is designed to bring the benefits of dependency injection to JavaScript projects, making it easier to manage dependencies and build scalable, maintainable applications. Explore the rest of the guides to learn more about the core concepts, advanced features, and practical examples. --- --- url: /guide/package/di-tools/analyzer/introduction.md --- # Introduction to `@wroud/di-tools-analyzer` ## Overview `@wroud/di-tools-analyzer` is a powerful tool designed to enhance your experience with the `@wroud/di` dependency injection library. This tool provides capabilities to analyze the service container built with `@wroud/di`, allowing you to gain deeper insights into your application's dependency graph. ## Key Features * **Data Collection**: Collects detailed information about the service container in a serializable JSON format. * **Visualization**: Utilizes D3.js to visualize the collected data, making it easier to understand the relationships and dependencies between services. * **Integration**: Seamlessly integrates with `@wroud/di`, providing an easy-to-use interface for analyzing and visualizing your service container. ## Why Use `@wroud/di-tools-analyzer`? 1. **Enhanced Understanding**: Visualizing your dependency graph helps you better understand how your services are interconnected, which is crucial for debugging and optimizing your application. 2. **Performance Optimization**: By analyzing the service container, you can identify potential performance bottlenecks and optimize your service resolutions. 3. **Documentation**: The tool provides a clear, visual representation of your service container, which can be used for documentation and onboarding new team members. ## How It Works `@wroud/di-tools-analyzer` works by hooking into the `@wroud/di` service container and collecting metadata about the registered services and their dependencies. This data is then transformed into a JSON format that can be easily consumed and visualized using D3.js. ## Getting Started To start using `@wroud/di-tools-analyzer`, follow these simple steps: ### Installation Install the `@wroud/di-tools-analyzer` package via npm or yarn. ::: code-group ```sh [npm] npm install @wroud/di-tools-analyzer ``` ```sh [yarn] yarn add @wroud/di-tools-analyzer ``` ```sh [pnpm] pnpm add @wroud/di-tools-analyzer ``` ```sh [bun] bun add @wroud/di-tools-analyzer ``` ::: ### Setup Integrate `@wroud/di-tools-analyzer` with your existing `@wroud/di` setup. ```javascript import { ServiceContainerBuilder } from "@wroud/di"; import { getDependenciesGraph } from "@wroud/di-tools-analyzer"; const builder = new ServiceContainerBuilder(); // Register your services const data = await getDependenciesGraph(builder); ``` #### Clusters based on Modules You can visualize clusters from ModuleRegistry ```javascript import { ServiceContainerBuilder, ModuleRegistry } from "@wroud/di"; import { getDependenciesGraph, ServiceCollectionProxy, } from "@wroud/di-tools-analyzer"; const builder = new ServiceContainerBuilder(); const builderProxy = new ServiceCollectionProxy(builder); // [!code ++] for (const module of ModuleRegistry) { await module.configure(builder); // [!code --] await module.configure(builderProxy.proxy(module.name)); // [!code ++] } const data = await getDependenciesGraph(builder); // [!code --] const data = await getDependenciesGraph(builder, builderProxy); // [!code ++] ``` ### Visualization Use the collected data to create visualizations. ```javascript import { createChart } from "@wroud/di-tools-analyzer"; const svg = document.createElement("svg"); const width = 512; const height = 512; const chart = createChart(svg, width, height); chart.update(data); ``` ### Basic Usage Example Here is a basic usage example of the analyzer integrated with D3.js for visualization: ```javascript import { ServiceContainerBuilder } from "@wroud/di"; import { createChart, getDependenciesGraph } from "@wroud/di-tools-analyzer"; // Assume htmlSvgElement, width, and height are predefined const chart = createChart(htmlSvgElement, width, height); // Initialize D3.js const builder = new ServiceContainerBuilder(); // Register your services const graph = await getDependenciesGraph(builder); // Collect information about dependencies, data can be serialized with JSON.stringify() chart.update(graph); // Render graph ``` ## Conclusion `@wroud/di-tools-analyzer` is an essential tool for developers using the `@wroud/di` library. It not only helps in understanding and optimizing your service container but also provides valuable visual documentation of your application's dependencies. For detailed usage examples and API documentation, refer to the [official documentation](#). --- --- url: /guide/package/di/scaling/introduction.md --- # Introduction to Scaling Your Application with @wroud/di Scaling applications efficiently can often present challenges, especially when using dependency injection (DI) frameworks. One of the common issues developers face is managing and registering dependencies across different modules and ensuring these dependencies are available when needed. As your application grows, maintaining a scalable and manageable DI setup becomes crucial. With @wroud/di, these scaling issues are not as significant as they might seem. The framework provides a robust solution to manage and register modules through its `ModuleRegistry` class. This powerful tool ensures your application remains modular and your dependencies are well-organized and accessible. ## Common Problems with Scaling Applications Using Dependency Injection 1. **Module Management**: Keeping track of numerous modules and their dependencies can become cumbersome. 2. **Dependency Registration**: Ensuring that all necessary dependencies are registered correctly and available throughout the application lifecycle. 3. **Initialization Phase Tracking**: Managing the initialization phase to ensure dependencies are registered before they are required. 4. **Manual Configuration**: The need to manually configure and add modules to the DI container, which can be error-prone if not handled correctly. ## Solutions with @wroud/di @wroud/di provides several mechanisms to alleviate these issues and streamline the scaling process: * **ModuleRegistry**: A static class that manages module registration and access. * **Automatic Module Tracking**: Listeners that help track modules registered after the DI container is built, ensuring correct initialization. * **Modular Approach**: Encourages a modular approach by using package names as module identifiers, simplifying module management in a monorepo setup. * **Side Effects Management**: Using `sideEffects` in `package.json` to ensure modules are registered correctly when imported. By leveraging these features, you can build a scalable and maintainable DI setup that grows with your application. ## Analyzing Dependencies with @wroud/di-tools-analyzer To further assist with scaling your application, @wroud/di-tools-analyzer can be used to analyze dependencies. This tool provides methods to collect dependency data in a serializable format (JSON) and visualize this data using D3.js. By analyzing your dependencies, you can identify potential issues and optimize your DI configuration, making it easier to scale your application effectively. In the following sections, we will explore how to implement and utilize the `ModuleRegistry` class, manage module registration, and ensure your application scales effectively with @wroud/di. --- --- url: /legal.md description: Legal information for Wroud Foundation LLC --- # Legal Wroud Foundation LLC, a Wyoming limited liability company. Registered office:\ 30 N Gould St, Ste N\ Sheridan, WY 82801\ United States --- --- url: /guide/package/di-tools/analyzer/live-demo.md --- # Live Demo --- --- url: /guide/package/di/advanced-features/manual-service-registration.md --- # Manual Service Registration In this guide, we will explore how to manually register services and their dependencies using @wroud/di, without relying on decorators. This approach is useful if you prefer explicit service registration, if your project does not support decorators, or if you need to integrate external libraries with dependency injection. ## What is Manual Service Registration? Manual service registration involves explicitly defining the dependencies of your services and registering them with the service container. This approach provides greater control over how services are configured and resolved. ## Registering Services To manually register services, you can use the `ServiceContainerBuilder` and `ServiceRegistry` classes. ### Example: Registering Services Manually ```javascript import { ServiceContainerBuilder, createService, constructor, factory } from '@wroud/di'; class Logger { log(message: string) { console.log(message); } } class UserService { constructor(private logger: Logger) { this.logger = logger; } createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name}`); // User creation logic } } function loggerInterop(logger: Logger) { return { log: (message: string) => logger.log(message); } } const loggerInterface = createService('LoggerInterface'); const containerBuilder = new ServiceContainerBuilder(); // Register services in the container builder containerBuilder .addSingleton(Logger, constructor(Logger)) .addSingleton(loggerInterface, factory(loggerInterop, Logger)) .addTransient(UserService, constructor(UserService, Logger)); const serviceProvider = containerBuilder.build(); const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` ### Example: Using Interfaces with Manual Registration When using interfaces, you can create service tokens to register and resolve services. ```typescript import { ServiceContainerBuilder, ServiceRegistry, createService, all, } from "@wroud/di"; interface ILogger { log(message: string): void; } const ILogger = createService("ILogger"); class ConsoleLogger implements ILogger { log(message: string) { console.log(`ConsoleLogger: ${message}`); } } class FileLogger implements ILogger { log(message: string) { // Assume logging to a file console.log(`FileLogger: ${message}`); } } class LoggingService { constructor(private loggers: ILogger[]) { this.loggers = loggers; } logToAll(message: string) { this.loggers.forEach((logger) => logger.log(message)); } } // Register services in the service registry ServiceRegistry.register(ConsoleLogger, { name: "ConsoleLogger", dependencies: [], }); ServiceRegistry.register(FileLogger, { name: "FileLogger", dependencies: [] }); ServiceRegistry.register(LoggingService, { name: "LoggingService", dependencies: [all(ILogger)], }); const containerBuilder = new ServiceContainerBuilder(); // Register services in the container builder containerBuilder.addSingleton(ILogger, ConsoleLogger); containerBuilder.addSingleton(ILogger, FileLogger); containerBuilder.addTransient(LoggingService); const serviceProvider = containerBuilder.build(); const loggingService = serviceProvider.getService(LoggingService); loggingService.logToAll("This is a test message"); ``` ## Integrating External Libraries Manual service registration is particularly useful when you need to integrate external libraries into your dependency injection system. This allows you to manage the lifecycle and dependencies of external services seamlessly. ### Example: Integrating an External Library (TypeScript) Suppose you are using an external library for HTTP requests. You can register a class from this library as a service and define its dependencies. ```typescript import { ServiceContainerBuilder, constructor, createService, factory, } from "@wroud/di"; // Define a service token for the API key const ApiKey = createService("ApiKey"); // Define a service token for the API key type ExternalHttpClientAuth = typeof getAuthentication; const ExternalHttpClientAuth = createService("HttpClientAuth"); // External library class class ExternalHttpClient { constructor(private apiKey: string) {} request(url: string) { // Make HTTP request using apiKey } } // External library function function getAuthentication(private httpClient: ExternalHttpClient) { // internal implementation } // Application service that depends on the external library class ApiService { constructor(private httpClient: ExternalHttpClient, private auth: ExternalHttpClientAuth) {} async auth(login: string, password: string) { await this.auth.login(login, password); } fetchData(endpoint: string) { return this.httpClient.request(endpoint); } } const containerBuilder = new ServiceContainerBuilder(); // Register services in the container builder containerBuilder .addSingleton(ApiKey, "your-api-key") .addSingleton(ExternalHttpClient, constructor(ExternalHttpClient, ApiKey)) .addSingleton(ExternalHttpClientAuth, factory(setAuthentication, ExternalHttpClient)) .addTransient(ApiService, constructor(ApiService, ExternalHttpClient)); const serviceProvider = containerBuilder.build(); const apiService = serviceProvider.getService(ApiService); await apiService.auth(env['login'], env['password']) apiService.fetchData("https://api.example.com/data"); ``` In this TypeScript example, the `ExternalHttpClient` class from an external library is registered with the service registry and the service container. The `ApiService` class can then depend on the `ExternalHttpClient`, allowing for seamless integration of the external library within the dependency injection system. ## Benefits of Manual Service Registration * **Explicit Dependency Configuration**: Manually define and register services and their dependencies, offering clear visibility and control over the service setup. * **Integration with External Libraries**: Easily integrate external libraries by manually registering their classes and defining dependencies explicitly. * **No Decorator Dependency**: Ideal for projects that do not support decorators or prefer a different approach to dependency registration. * **Compatibility with Interfaces**: Use service tokens to register and resolve services, making it easy to manage dependencies for interfaces. ## Conclusion Manual service registration in @wroud/di provides a flexible and explicit way to manage dependencies in your application. By using `ServiceContainerBuilder` and `ServiceRegistry`, you can easily register and resolve services, enabling you to build modular and maintainable applications. --- --- url: /packages/overview.md --- # Overview The Wroud Foundation offers a suite of tools to help developers implement best practices and efficient patterns in JavaScript applications. Our tools focus on modularity, ease of use, and performance. ## Available Packages * **@wroud/di**: A lightweight dependency injection library for JavaScript inspired by [.NET's DI](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) system. Written in TypeScript, it supports modern JavaScript features, including decorators, and provides robust dependency management capabilities. * **@wroud/vite-plugin-tsc**: Run `tsc` with Vite to check types that esbuild misses or transpile TypeScript files while keeping Vite features. * **@wroud/react-split-view**: A React hook for building resizable split panes with optional sticky edges. * **@wroud/react-tree**: A virtualized tree component for React. * **Other Packages**: More tools to come, each aimed at addressing specific challenges in JavaScript development. # Navigation Use the sidebar to navigate through the documentation. Each package has its own section with detailed guides and API references. Here’s a quick overview of what you’ll find: ## Dependency Injection (`@wroud/di`) * **[Overview](./di/overview)**: Introduction and key features. * **[Installation](./di/install)**: Step-by-step guide to installing the package. * **[Usage](./di/usage)**: Examples of how to use the package in different environments. * **[API](./di/api)**: Detailed reference of the API provided by the package. ### React Integration (`@wroud/di-react`) * **[Overview](./di/integrations/react/overview)**: Introduction and key features. * **[Installation](./di/integrations/react/install)**: Step-by-step guide to setting it up in your React application. * **[Usage](./di/integrations/react/usage)**: Practical examples and patterns for using it within React components. * **[API](./di/integrations/react/api)**: Complete API reference. ## React Split View (`@wroud/react-split-view`) * **[Overview](./react-split-view/overview)**: Introduction and key features. * **[Installation](./react-split-view/install)**: Steps to add it to your project. * **[Usage](./react-split-view/usage)**: Examples for horizontal and vertical splits. * **[API](./react-split-view/api)**: Complete API reference. ## React Tree (`@wroud/react-tree`) * **[Overview](./react-tree/overview)**: Introduction and key features. * **[Installation](./react-tree/install)**: Steps to add it to your project. * **[Usage](./react-tree/usage)**: Examples of basic setup and lazy loading. * **[API](./react-tree/api)**: Complete API reference. ## Future Tools As we expand our toolset, you will find new sections dedicated to each package with similar documentation structures to ensure a consistent and straightforward experience. # Getting Started To get started with any of our tools, simply select the package you’re interested in from the sidebar. Each section is designed to guide you from installation to advanced usage, ensuring you can leverage the full potential of the Wroud Foundation tools in your projects. *** For any questions or contributions, please visit our [GitHub repository](https://github.com/wroud/foundation). We welcome feedback and collaboration to improve and expand our offerings. --- --- url: /packages.md --- # Packages --- --- url: /packages/react-split-view/overview.md --- # React Split View ## Overview `@wroud/react-split-view` is a lightweight React hook for creating resizable split panes. It supports horizontal and vertical layouts with minimal configuration and no external dependencies. ## Key Features * **Simple API**: Manage split views with a single hook. * **Lightweight**: Small bundle size and zero dependencies. * **Flexible Layouts**: Horizontal, vertical, and nested splits. * **Sticky Edges**: Optional snap-to-edge behavior while dragging. * **TypeScript**: Written in TypeScript for a fully typed API. --- --- url: /packages/react-tree/overview.md --- # React Tree `@wroud/react-tree` is a virtualized tree component for React. It focuses on performance and customization so you can efficiently render large hierarchies. ## Key Features * **Virtualization** for rendering only the visible portion of large trees. * **Node Selection** built in through the `useTree` hook. * **Custom Renderers** to override node controls or content. * **Async Loading** support for fetching children on demand. * **CSS Customization** via class maps and optional default styles. --- --- url: /guide/package/di/core-concepts/service-container.md --- # Service Container The service container is at the heart of the @wroud/di library, enabling you to manage and resolve dependencies in your application. This guide will help you understand how to register and resolve services using the `ServiceContainerBuilder` and `IServiceProvider`. ## What is a Service Container? A service container, also known as a dependency injection container, is a design pattern used to manage the creation, configuration, and resolution of dependencies in a software application. It helps in achieving loose coupling, improving testability, and enhancing maintainability. ## How to Register Services Service registration involves adding your services to the service container so they can be resolved when needed. @wroud/di provides several methods for registering services, depending on their intended lifetimes: singleton, transient, and scoped. ### Singleton Services Singleton services are created once and shared throughout the application. To register a singleton service, use the `addSingleton` method. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); const serviceProvider = containerBuilder.build(); const logger1 = serviceProvider.getService(Logger); const logger2 = serviceProvider.getService(Logger); console.log(logger1 === logger2); // true ``` ### Transient Services Transient services are created each time they are requested. To register a transient service, use the `addTransient` method. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addTransient(Logger); const serviceProvider = containerBuilder.build(); const logger1 = serviceProvider.getService(Logger); const logger2 = serviceProvider.getService(Logger); console.log(logger1 === logger2); // false ``` ### Scoped Services Scoped services are created once per scope. This is useful in scenarios like web requests where you want to share services within a specific context. To register a scoped service, use the `addScoped` method. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addScoped(Logger); const serviceProvider = containerBuilder.build(); const scope1 = serviceProvider.createScope(); const scope2 = serviceProvider.createScope(); const logger1 = scope1.serviceProvider.getService(Logger); const logger2 = scope2.serviceProvider.getService(Logger); console.log(logger1 === logger2); // false const logger3 = scope1.serviceProvider.getService(Logger); console.log(logger1 === logger3); // true ``` ## How to Resolve Dependencies Dependency resolution is the process of retrieving an instance of a registered service. @wroud/di automatically handles the resolution of dependencies based on the service registration and their lifetimes. ### Constructor Injection Constructor injection is the primary way to inject dependencies in @wroud/di. Dependencies are declared in the constructor and resolved automatically by the container. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } @injectable(() => [Logger]) class UserService { constructor(private logger: Logger) { this.logger = logger; } createUser(user: { name: string }) { this.logger.log(`Creating user: ${user.name}`); // User creation logic } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); containerBuilder.addTransient(UserService); const serviceProvider = containerBuilder.build(); const userService = serviceProvider.getService(UserService); userService.createUser({ name: 'John Doe' }); ``` ## Benefits of Using a Service Container * **Loose Coupling**: Services are decoupled from their dependencies, allowing for easier modifications and replacements. * **Improved Testability**: Dependencies can be easily mocked or stubbed during testing. * **Enhanced Maintainability**: Clear dependency management leads to more maintainable codebases. ## Conclusion The service container in @wroud/di is a powerful tool that enables effective dependency management. By understanding how to register and resolve services, you can leverage dependency injection to create flexible and maintainable applications. --- --- url: /guide/package/di/advanced-features/service-disposal.md --- # Service Disposal Service disposal in @wroud/di is crucial for managing the lifecycle of services, especially those that hold resources like file handles or database connections. This guide will show you how to set up and use service disposal mechanisms to ensure proper resource management in your application. ## What is Service Disposal? Service disposal involves properly releasing resources held by services when they are no longer needed. This is particularly important for services that manage external resources like files, network connections, or database connections. ## Setting Up Service Disposal @wroud/di uses the TC39 proposal for explicit resource management to facilitate service disposal. You can choose between automatic disposal using the `using` keyword or manual disposal. ::: details For more information about the `using` keyword and explicit resource management, refer to the [TC39 proposal for explicit resource management](https://github.com/tc39/proposal-explicit-resource-management). ::: ### Service Lifetimes and Disposal * **Transient**: Transient services are created each time they are requested. It is the user's responsibility to dispose of them manually. * **Scoped**: Scoped services are created once per request or scope. They are disposed of automatically at the end of the request or scope. * **Singleton**: Singleton services are created the first time they are requested and live for the duration of the application's lifetime. They are disposed of automatically when the service provider is disposed. ### Automatic Disposal With the `using` keyword, services are automatically disposed of when they go out of scope. #### Example: Automatic Disposal ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class DatabaseConnection { connect() { console.log("Database connected"); } [Symbol.dispose]() { console.log("Database connection closed"); } } const builder = new ServiceContainerBuilder(); builder.addSingleton(DatabaseConnection); using serviceProvider = builder.build(); const dbConnection = serviceProvider.getService(DatabaseConnection); dbConnection.connect(); // When the serviceProvider goes out of scope, the DatabaseConnection will be disposed of automatically ``` ### Asynchronous Disposal For services that require asynchronous cleanup, use `Symbol.asyncDispose`. You can also name the function `dispose`; it will be used as a fallback if `Symbol.asyncDispose` is not presented. #### Example: Asynchronous Disposal ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class AsyncService { async init() { console.log("AsyncService initialized"); } async [Symbol.asyncDispose]() { console.log("AsyncService cleaned up asynchronously"); } } const builder = new ServiceContainerBuilder(); builder.addSingleton(AsyncService); await using serviceProvider = builder.build(); const asyncService = serviceProvider.getService(AsyncService); await asyncService.init(); // When the serviceProvider goes out of scope, the AsyncService will be disposed of automatically ``` ### Manual Disposal of Transient Services Transient services must be disposed of manually by the user. Here's how you can do it: #### Example: Manual Disposal of Transient Services ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class Logger { log(message: string) { console.log(message); } // you also can use dispose() function it will be used as a fallback if `Symbol.dispose` not presented [Symbol.dispose]() { console.log("Logger disposed"); } } const builder = new ServiceContainerBuilder(); builder.addTransient(Logger); const serviceProvider = builder.build(); using logger = serviceProvider.getService(Logger); logger.log("This is a log message"); // When the logger goes out of scope, the Logger will be disposed of automatically ``` ### Disposal of Scoped Services Scoped services are disposed of at the end of a request or scope. Here's how you can manage scoped services: #### Example: Automatic Disposal of Scoped Services ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class RequestHandler { handle() { console.log("Handling request"); } // you also can use dispose() function it will be used as a fallback if `Symbol.dispose` not presented [Symbol.dispose]() { console.log("RequestHandler disposed"); } } const builder = new ServiceContainerBuilder(); builder.addScoped(RequestHandler); using serviceProvider = builder.build(); function handleRequest() { using scope = serviceProvider.createScope(); const requestHandler = scope.serviceProvider.getService(RequestHandler); requestHandler.handle(); // When the scope goes out of scope, the RequestHandler will be disposed of automatically } handleRequest(); ``` ## Manual Disposal Manual disposal requires the developer to explicitly call the disposal methods (`[Symbol.dispose]()` or `[Symbol.asyncDispose]()`) without using the `using` keyword. ### Manual Disposal Manual disposal requires you to explicitly call the `[Symbol.dispose]()` method. #### Example: Manual Disposal ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class Cache { clear() { console.log("Cache cleared"); } [Symbol.dispose]() { console.log("Cache disposed"); } } const builder = new ServiceContainerBuilder(); builder.addSingleton(Cache); const serviceProvider = builder.build(); const cache = serviceProvider.getService(Cache); cache.clear(); // Manually dispose of the service provider serviceProvider[Symbol.dispose](); ``` ### Manual Asynchronous Disposal Manual asynchronous disposal requires you to explicitly call the `[Symbol.asyncDispose]()` method. #### Example: Manual Asynchronous Disposal ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class AsyncProcessor { async process() { console.log("Processing asynchronously"); } async [Symbol.asyncDispose]() { console.log("AsyncProcessor cleaned up asynchronously"); } } const builder = new ServiceContainerBuilder(); builder.addSingleton(AsyncProcessor); const serviceProvider = builder.build(); const asyncProcessor = serviceProvider.getService(AsyncProcessor); await asyncProcessor.process(); // Manually dispose of the service provider await serviceProvider[Symbol.asyncDispose](); ``` ## Real-World Example: Managing Database Connections Consider an application that manages database connections. Proper disposal of these connections is critical to avoid resource leaks. In this example, we will use a combination of singleton and transient services. ```typescript import { ServiceContainerBuilder, injectable } from "@wroud/di"; @injectable() class DatabaseConnection { connect() { console.log("Database connected"); } [Symbol.dispose]() { console.log("Database connection closed"); } } @injectable() class Logger { log(message: string) { console.log(message); } [Symbol.dispose]() { console.log("Logger disposed"); } } const builder = new ServiceContainerBuilder(); builder.addSingleton(DatabaseConnection); builder.addTransient(Logger); // Using automatic disposal for singleton service using serviceProvider = builder.build(); const dbConnection = serviceProvider.getService(DatabaseConnection); dbConnection.connect(); // Database connection will be automatically closed when the serviceProvider goes out of scope // Using manual disposal for transient services using logger = serviceProvider.getService(Logger); logger.log("This is a log message"); // When the logger goes out of scope, the Logger will be disposed of automatically ``` ## Benefits of Service Disposal * **Resource Management**: Ensures that resources such as file handles and database connections are properly released. * **Avoids Memory Leaks**: Proper disposal helps prevent memory leaks by ensuring that resources are not held longer than necessary. * **Enhanced Stability**: Proper resource management enhances the stability and reliability of your application. ## Conclusion Service disposal is a critical aspect of resource management in any application. By using the service disposal mechanisms in @wroud/di, you can ensure that resources are properly managed and released, enhancing the stability and performance of your application. --- --- url: /guide/package/di/core-concepts/service-lifetimes.md --- # Service Lifetimes Understanding service lifetimes is crucial when working with dependency injection. This guide will explain the different service lifetimes available in @wroud/di and how to use them effectively. ## What Are Service Lifetimes? Service lifetimes define how long a service instance should be kept alive. There are three main types of lifetimes in @wroud/di: * **Singleton**: A single instance is shared across the entire application. * **Transient**: A new instance is created every time the service is requested. * **Scoped**: A single instance is created and shared within a defined scope. ## Singleton Lifetime A singleton service is created once and shared throughout the application. This is useful for services that maintain state or need to be shared globally. ::: tip Use singletons for services that are expensive to create or need to maintain state, such as logging services or configuration settings. ::: ### Example: Logger Service A logging service is a good example of a singleton because you generally want all parts of your application to use the same logger instance. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class Logger { log(message: string) { console.log(message); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addSingleton(Logger); const serviceProvider = containerBuilder.build(); const logger1 = serviceProvider.getService(Logger); const logger2 = serviceProvider.getService(Logger); console.log(logger1 === logger2); // true ``` ## Transient Lifetime A transient service is created every time it is requested. This is useful for lightweight, stateless services. ::: info Transient services are ideal for services that do not hold state and are inexpensive to create. ::: ### Example: Email Service An email service that sends notifications can be a transient service because it doesn't need to maintain any state between uses. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class EmailService { sendEmail(recipient, subject, body) { console.log(`Sending email to ${recipient}: ${subject} - ${body}`); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addTransient(EmailService); const serviceProvider = containerBuilder.build(); const emailService1 = serviceProvider.getService(EmailService); const emailService2 = serviceProvider.getService(EmailService); console.log(emailService1 === emailService2); // false ``` ## Scoped Lifetime A scoped service is created once per scope and shared within that scope. This is useful in scenarios like web applications where you might want to share services within a single request. ::: details Scoped services are useful when you want to share a service instance within a specific context, such as a single web request. ::: ### Example: Database Context A database context can be a scoped service, ensuring that all database operations within a single request use the same context instance. ```javascript import { ServiceContainerBuilder, injectable } from '@wroud/di'; @injectable() class DbContext { constructor() { this.connection = createDatabaseConnection(); } } const containerBuilder = new ServiceContainerBuilder(); containerBuilder.addScoped(DbContext); const serviceProvider = containerBuilder.build(); const scope1 = serviceProvider.createScope(); const scope2 = serviceProvider.createScope(); const dbContext1 = scope1.serviceProvider.getService(DbContext); const dbContext2 = scope2.serviceProvider.getService(DbContext); console.log(dbContext1 === dbContext2); // false const dbContext3 = scope1.serviceProvider.getService(DbContext); console.log(dbContext1 === dbContext3); // true ``` ## Choosing the Right Lifetime ### When to Use Singleton * When the service is expensive to create. * When the service needs to maintain state. * When you need a single instance across the entire application. ### When to Use Transient * When the service is lightweight and stateless. * When you want to ensure a fresh instance every time. ### When to Use Scoped * When you need to share a service within a specific context. * When you want to control the lifetime of a service within a defined scope. ## Conclusion Understanding and using service lifetimes correctly is essential for efficient and maintainable dependency management. Choose the appropriate lifetime for your services based on their use case to optimize performance and resource usage. --- --- url: /guide/package/di/scaling/integration.md --- # Step-by-Step Guide to Integrate ModuleRegistry into Your Application Integrating the `ModuleRegistry` class into an existing application can greatly enhance the modularity and scalability of your dependency injection setup. This guide will walk you through the steps to integrate `ModuleRegistry` with a given starting example. ## Starting Example Let's begin with a sample application that utilizes `@wroud/di` for dependency injection. This example demonstrates how to set up a service container and register various services: ```ts import { ServiceContainerBuilder, injectable, createService } from "@wroud/di"; @injectable(() => []) class DatabaseConnection {} @injectable(() => [DatabaseConnection]) class Database {} @injectable(() => [Database]) class DBUsers {} @injectable(() => [Database]) class DBArticles {} @injectable(() => [Database]) class DBComments {} @injectable(() => []) class Request {} @injectable(() => [Request, DBUsers]) class Profile {} @injectable(() => [Database]) class SessionStore {} @injectable(() => [Request, SessionStore]) class Session {} @injectable(() => [DatabaseConnection, GQLServer]) class App {} @injectable(() => [Request]) class GQLServer {} const serviceCollection = new ServiceContainerBuilder() .addSingleton(App) .addSingleton(DatabaseConnection) .addSingleton(GQLServer) .addTransient(Database) .addTransient(SessionStore) .addTransient(DBUsers) .addTransient(DBArticles) .addTransient(DBComments) .addScoped(Request) .addScoped(Profile) .addScoped(Session); ``` In this example, we have a set of services such as `DatabaseConnection`, `Database`, and `App`, among others. These services are registered in the `ServiceContainerBuilder` with different lifetimes (`singleton`, `transient`, and `scoped`). ## Grouping Dependencies into Modules To manage these dependencies more efficiently, we can group related services into modules. This approach helps in organizing the code better and simplifies the registration process. Each module will represent a cohesive set of related services. ### Why Grouping Dependencies is Beneficial 1. **Code Organization**: Grouping related services into modules helps in maintaining a clean and organized codebase. It becomes easier to locate, manage, and update services related to a specific functionality. 2. **Scalability**: As your application grows, the number of services and their dependencies can become overwhelming. Grouping services into modules allows you to scale your application more effectively by isolating changes and updates to specific parts of the application. 3. **Reusability**: Modules can be reused across different parts of the application or even in different projects. This promotes code reuse and reduces duplication. 4. **Maintainability**: By grouping services into logical modules, maintaining and updating the code becomes more manageable. Changes to a specific functionality are confined to the respective module, reducing the risk of unintended side effects. ### Method for Grouping Dependencies We can achieve this by defining modules as collections of related services. Each module will have a unique name and a method to configure the services related to that module. Here are some suggested groupings for the given services: * **Core Module**: This module can contain core services that are fundamental to the application, such as the main application class, database connection, and any server configurations. * **Database Module**: This module can group all services related to database interactions, such as the database itself and entities that interact with the database. * **Session Module**: This module can include services related to user sessions and requests, such as session storage and profile management. By following this modular approach, you can ensure that your services are well-organized, easily maintainable, and scalable. ## Creating a Module To create a module, follow these steps: 1. **Group Related Services**: Organize related services by creating a new package for each module if you are using workspaces. This helps in keeping the services that belong to the same module together, making the code more organized and manageable. 2. **Create `module.ts`**: In the package where you have grouped the related services, create a file named `module.ts`. In this file, use `ModuleRegistry.add` to register the module. This will allow the module and its services to be recognized and managed by the `ModuleRegistry`. 3. **Import `module.ts` in `index.ts`**: In the same package, create an `index.ts` file and import `module.ts` to ensure the module is registered when the package is imported. 4. **Add `module.ts` and `index.ts` to "sideEffects"**: To ensure that the module is correctly registered when the package is imported, add `module.ts` and `index.ts` to the `sideEffects` field in your `package.json`. This step ensures that the module registration side effect is executed, allowing the module to be properly integrated into the application. ### Example: Core Module 1. **Group Related Services**: Create a package named `@my/core` and add the core services. 2. **Create `module.ts`**: In the `@my/core` package, create a `module.ts` file: ```ts import { ModuleRegistry } from "@wroud/di"; import { App } from "./App"; import { GQLServer } from "./GQLServer"; ModuleRegistry.add({ name: "@my/core", async configure(serviceCollection) { serviceCollection.addSingleton(App).addSingleton(GQLServer); }, }); ``` 3. **Create `index.ts`**: In the `@my/core` package, create an `index.ts` file and import `module.ts`: ```ts import "./module.ts"; ``` 4. **Add `module.ts` and `index.ts` to "sideEffects"**: In your `package.json` of the `@my/core` package, add the paths to `module.ts` and `index.ts`: ```json { "name": "@my/core", "version": "1.0.0", "main": "index.js", "sideEffects": ["./src/module.ts", "./src/index.ts"] } ``` ### Example: Database Module 1. **Group Related Services**: Create a package named `@my/database` and add the database-related services. 2. **Create `module.ts`**: In the `@my/database` package, create a `module.ts` file: ```ts import { ModuleRegistry } from "@wroud/di"; import { Database } from "./Database"; import { DBUsers } from "./DBUsers"; import { DBArticles } from "./DBArticles"; import { DBComments } from "./DBComments"; import { DatabaseConnection } from "./DatabaseConnection"; ModuleRegistry.add({ name: "@my/database", async configure(serviceCollection) { serviceCollection .addTransient(Database) .addTransient(DBUsers) .addTransient(DBArticles) .addTransient(DBComments) .addSingleton(DatabaseConnection); }, }); ``` 3. **Create `index.ts`**: In the `@my/database` package, create an `index.ts` file and import `module.ts`: ```ts import "./module.ts"; ``` 4. **Add `module.ts` and `index.ts` to "sideEffects"**: In your `package.json` of the `@my/database` package, add the paths to `module.ts` and `index.ts`: ```json { "name": "@my/database", "version": "1.0.0", "main": "index.js", "sideEffects": ["./src/module.ts", "./src/index.ts"] } ``` ### Example: Session Module 1. **Group Related Services**: Create a package named `@my/session` and add the session-related services. 2. **Create `module.ts`**: In the `@my/session` package, create a `module.ts` file: ```ts import { ModuleRegistry } from "@wroud/di"; import { Request } from "./Request"; import { Session } from "./Session"; import { SessionStore } from "./SessionStore"; import { Profile } from "./Profile"; ModuleRegistry.add({ name: "@my/session", async configure(serviceCollection) { serviceCollection .addTransient(SessionStore) .addScoped(Request) .addScoped(Profile) .addScoped(Session); }, }); ``` 3. **Create `index.ts`**: In the `@my/session` package, create an `index.ts` file and import `module.ts`: ```ts import "./module.ts"; ``` 4. **Add `module.ts` and `index.ts` to "sideEffects"**: In your `package.json` of the `@my/session` package, add the paths to `module.ts` and `index.ts`: ```json { "name": "@my/session", "version": "1.0.0", "main": "index.js", "sideEffects": ["./src/module.ts", "./src/index.ts"] } ``` By following these steps for each module, you can create and register modules in a structured manner, making your application more modular and easier to manage. In the next section, we will discuss how to use the `ModuleRegistry` to configure the service container. ## Initializing the Service Container Now that we have organized our services into modules and registered them with `ModuleRegistry`, the next step is to create an entry point for our application and use `ServiceContainerBuilder` with `ModuleRegistry` to initialize the service collection. ### Creating the Entry Point 1. **Create an Entry Point File**: Create a new file named `main.ts` or `index.ts` in the root of your application. This file will serve as the entry point for your application. 2. **Initialize ServiceContainerBuilder**: In the entry point file, use `ServiceContainerBuilder` to initialize the service collection. Iterate over the modules registered in `ModuleRegistry` and configure the service collection. ### Example: Entry Point Here is an example of how to create the entry point and initialize the service collection: ```ts import { ServiceContainerBuilder, ModuleRegistry } from "@wroud/di"; import { App } from "@my/core"; // Create a new ServiceContainerBuilder instance const builder = new ServiceContainerBuilder(); // Iterate over the registered modules in ModuleRegistry for (const module of ModuleRegistry) { await module.configure(builder); } // Build the service container const serviceProvider = builder.build(); // Now you can resolve and use your services const app = serviceProvider.getService(App); app.start(); ``` In this example: 1. We create a new instance of `ServiceContainerBuilder`. 2. We iterate over the modules registered in `ModuleRegistry` and call their `configure` method to register their services with the service collection. 3. We build the service container using the `build` method of `ServiceContainerBuilder`. 4. We resolve the `App` service from the service provider and start the application. By following these steps, you can ensure that all your services are properly registered and configured, and your application is ready to run. ### How It Works In this setup, modules are registered automatically due to the way we have structured our imports and module initialization: 1. **Automatic Module Registration**: Each module's `module.ts` is imported in the package's main file `index.ts`. This means that whenever anything is imported from these packages, their respective modules are registered automatically. For example: ```ts // @my/core/index.ts import "./module.ts"; // @my/database/index.ts import "./module.ts"; // @my/session/index.ts import "./module.ts"; ``` 2. **Dependency Chain**: In our example, the `App` class has a dependency on `DatabaseConnection` which causes the `@my/database` module to be registered. Additionally, `App` also depends on `GQLServer`, which in turn depends on `Request`, causing the `@my/session` module to be registered. * **Core Module**: We imported `App` in our entry point, which registered `@my/core`. * **Database Module**: `App` depends on `DatabaseConnection`, which triggers the registration of `@my/database`. * **Session Module**: `App` also has a dependency on `GQLServer`, which depends on `Request`, triggering the registration of `@my/session`. By importing the main files of each package, we ensure that all necessary modules are registered without explicitly calling their registration code in the entry point. This method simplifies the initialization process and ensures that all dependencies are properly configured. By following these steps, you can ensure that all your services are properly registered and configured, and your application is ready to run. ## Conclusion In this guide, we have demonstrated how to integrate the `ModuleRegistry` from `@wroud/di` into an existing application to manage and scale your dependencies effectively. By organizing your services into cohesive modules and using the `ModuleRegistry` to handle their registration, you can achieve a more modular, scalable, and maintainable application architecture. ### Key Takeaways 1. **Modular Organization**: Group related services into modules to maintain a clean and organized codebase. 2. **Automatic Module Registration**: Use `module.ts` and import it in the package's main file (`index.ts`) to ensure modules are registered automatically when the package is imported. 3. **ServiceContainerBuilder Integration**: Initialize the service container by iterating over the registered modules and configuring the service collection using `ServiceContainerBuilder`. 4. **Dependency Chain Management**: Leverage the dependency chain to automatically register necessary modules based on the services' dependencies. By following these best practices, you can streamline your application's dependency injection setup, making it easier to manage and scale as your application grows. The `ModuleRegistry` provides a robust solution for handling the complexities of dependency management in large-scale applications, ensuring that your services are properly registered and accessible throughout the application lifecycle. --- --- url: /packages/di/integrations/react/usage.md --- # Usage This page provides a practical example of how to use `@wroud/di-react` for dependency injection in a React application. We’ll walk through setting up a service container, resolving services using React hooks, and leveraging lazy loading with React Suspense for improved performance. ## Setting Up the Service Container To begin, we create a service container using `ServiceContainerBuilder` from `@wroud/di`. This allows us to register services that will be injected later into our React components. ```tsx twoslash import { ServiceContainerBuilder } from "@wroud/di"; import { ServiceProvider } from "@wroud/di-react"; // Create the service container const builder = new ServiceContainerBuilder(); // Example service class Logger { log(message: string) { console.log(message); } } // Register the service as a singleton builder.addSingleton(Logger); // Build the service provider const serviceProvider = builder.build(); ``` ## Providing Services in React Next, we use the `ServiceProvider` component to wrap the application, making the registered services available throughout the component tree. ```tsx import React from "react"; import { ServiceProvider } from "@wroud/di-react"; import AppContent from "./AppContent"; function App() { return ( ); } export default App; ``` ## Resolving Services with `useService` Within your components, you can resolve services using the `useService` hook. Here’s an example where the `Logger` service is used in a component: ```tsx import React from "react"; import { useService } from "@wroud/di-react"; import Logger from "./Logger"; function AppContent() { const logger = useService(Logger); function handleClick() { logger.log("Button clicked!"); } return ; } export default AppContent; ``` ## Lazy Loading Services with React Suspense `@wroud/di-react` supports lazy loading of services, allowing you to defer the loading of large or rarely used services until they are needed. This is especially useful for improving performance in larger applications. React's Suspense mechanism is automatically used to handle loading states. Here’s an example of how to set up lazy-loaded services using `@wroud/di`'s `lazy` method: ```tsx import { lazy, ServiceContainerBuilder } from "@wroud/di"; import { CounterService } from "./CounterService"; import { ILoggerService } from "./ILoggerService"; import { ConsoleLoggerService } from "./ConsoleLogService"; import { IAdministrationService } from "./administration/IAdministrationService"; // Create the service provider with lazy-loaded services export function createServiceProvider() { const serviceProvider = new ServiceContainerBuilder() .addSingleton(CounterService) .addSingleton(ILoggerService, ConsoleLoggerService) .addSingleton( IAdministrationService, lazy(() => import("./administration/AdministrationService").then( (m) => m.AdministrationService, ), ), ) .build(); return serviceProvider; } ``` [Try it in the Playground](https://stackblitz.com/edit/wroud-di-react-lazy?file=src%2Fservices%2FcreateServiceProvider.ts) In this example, the `AdministrationService` is lazily loaded. When the `IAdministrationService` is requested via `useService`, the loading will be handled using React Suspense, showing a fallback UI while the service is being loaded. To resolve the lazy-loaded service in a component, you can use `useService`, and React Suspense will automatically handle the asynchronous nature of the service: ```tsx import React, { Suspense } from "react"; import { useService, ServiceProvider } from "@wroud/di-react"; import { IAdministrationService } from "./administration/IAdministrationService"; import { createServiceProvider } from "./createServiceProvider"; const serviceProvider = createServiceProvider(); function AdministrationComponent() { const adminService = useService(IAdministrationService); return
{adminService.getAdminData()}
; } function App() { return ( Loading administration service...}> ); } export default App; ``` --- --- url: /packages/di/usage.md --- # Usage This guide will show you how to use the `@wroud/di` library in different setups: with [decorators (stage 3)](https://github.com/tc39/proposal-decorators), legacy decorators (stage 2), and plain JavaScript. Each section provides configuration tips and example code to help you get started quickly. ```ts twoslash import { injectable, createService, ServiceContainerBuilder } from "@wroud/di"; interface ILoggerService { log(message: string): void; } const ILoggerService = createService("ILoggerService"); @injectable() class ConsoleLoggerService implements ILoggerService { log(message: string) { console.log(message); } } @injectable(() => [ILoggerService]) class CounterService { constructor(private logger: ILoggerService) {} action() { this.logger.log("Action executed"); } } const serviceProvider = new ServiceContainerBuilder() .addSingleton(CounterService) .addSingleton(ILoggerService, ConsoleLoggerService) .build(); const counter = serviceProvider.getService(CounterService); ``` [Try it in the Playground](https://stackblitz.com/edit/wroud-di-decorators?file=src%2Fcounter.ts) ## Decorators (stage 3) [TypeScript Documentation](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators) To use the latest decorator features in `@wroud/di`, you can utilize the stage 3 decorators. The `@injectable` decorator allows you to specify class constructor dependencies so they can be automatically injected by the DI system. ### Configuration To use stage 3 decorators, ensure you have TypeScript version 5.0.0 or higher. Update your `tsconfig.json` file as follows: ```json { "compilerOptions": { "target": "ESNext", "lib": ["ESNext"], // or "Decorators" "experimentalDecorators": false, "emitDecoratorMetadata": false } } ``` ::: details Vite config [Browsers is not supporting decorators](https://caniuse.com/decorators) yet so you need to downgrade target to es2023 or lower to run it in dev. ```diff import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ + esbuild:{ + target: "es2023" + }, plugins: [react()], }); ``` ::: ::: details Migration from Legacy Decorators (stage 2) If you are migrating from legacy decorators (stage 2), you need to update your `tsconfig.json`: ```diff { "compilerOptions": { - "experimentalDecorators": true, - "emitDecoratorMetadata": true, + "lib": ["ESNext"], // or "Decorators" } } ``` ::: ::: details You might need to install `tslib` if your target environment does not support decorators. ::: code-group ```sh [npm] npm install tslib ``` ```sh [yarn] yarn add tslib ``` ```sh [pnpm] pnpm add tslib ``` ```sh [bun] bun add tslib ``` ::: ## Legacy Decorators (stage 2) If you prefer or need to use legacy decorators, you can still use `@wroud/di` with them. The code structure remains the same. [Try it in the Playground](https://stackblitz.com/edit/wroud-di-legacy-decorators?file=src%2Fcounter.ts) ### Configuration Enable legacy decorators in your `tsconfig.json`: ```json [tsconfig.json] { "compilerOptions": { "target": "ES5", "experimentalDecorators": true } } ``` ## Plain JS To use `@wroud/di` without decorators, you can manually register class dependencies using `ServiceRegistry`. This method is just as effective and allows you to manage dependencies without relying on decorators. ```ts twoslash import { createService, ServiceContainerBuilder, constructor } from "@wroud/di"; interface ILoggerService { log(message: string): void; } const ILoggerService = createService("ILoggerService"); class ConsoleLoggerService implements ILoggerService { log(message: string) { console.log(message); } } class CounterService { constructor(private logger: ILoggerService) {} action() { this.logger.log("Action executed"); } } function configure() { const serviceProvider = new ServiceContainerBuilder() .addSingleton(CounterService, constructor(CounterService, ILoggerService)) .addSingleton(ILoggerService, constructor(ConsoleLoggerService)) .build(); const counter = serviceProvider.getService(CounterService); } ``` [Try it in the Playground](https://stackblitz.com/edit/wroud-di-no-decorators?file=src%2Fcounter.ts) --- --- url: /packages/react-split-view/usage.md --- # Usage The `useSplitView` hook provides everything needed to create resizable split panes. It returns props that can be spread on your elements to enable drag-to-resize functionality. ## Horizontal Split (default) ```tsx import { useSplitView } from "@wroud/react-split-view"; function Example() { const split = useSplitView(); return (
Left
Right
); } ``` Required CSS: ```css .container { display: flex; width: 100%; height: 100%; } .panel { flex: 1; overflow: auto; } .sash { width: 4px; background-color: #ccc; cursor: ew-resize; } ``` ## Vertical Split ```tsx function Vertical() { const split = useSplitView(); return (
Top
Bottom
); } ``` ## Advanced Features ### Sticky Edges Enable snapping when the sash is near the container edge. ```tsx const split = useSplitView({ sticky: 20, // pixels from the edge }); ``` ### Multiple Splits ```tsx function MultipleSplits() { const first = useSplitView(); const second = useSplitView(); return (
Left
Top Right
Bottom Right
); } ``` --- --- url: /packages/react-tree/usage.md --- # Usage This example shows a basic tree setup using the `useTree` hook and the `Tree` component. Node height is stored in a reactive value so virtualization can calculate offsets. ```tsx import { Tree, useTree } from "@wroud/react-tree"; import { useCreateReactiveValue } from "@wroud/react-reactive-value"; const data = { rootId: "root", getNode(id: string) { return { name: id === "root" ? "Root" : `Node ${id}` }; }, getChildren(id: string) { return id === "root" ? ["a", "b", "c"] : []; }, getState() { return { expanded: true, selected: false }; }, updateState() {}, updateStateAll() {}, }; function App() { const nodeHeight = useCreateReactiveValue(() => 24, null, []); const tree = useTree({ data }); return (
); } ``` ### Custom Control Renderer You can override how each node is displayed by providing a `controlRenderer`. ```tsx import { TreeNodeControl, TreeNodeExpand, TreeNodeName, } from "@wroud/react-tree"; function CustomControl({ nodeId }) { return ( {`Custom ${nodeId}`} ); } ; ``` ### Lazy Loading `getChildren` can return a promise so nodes load as needed: ```ts const tree = useTree({ data: { rootId: "root", async getChildren(id) { return fetchChildren(id); }, getNode() { return { name: "" }; }, getState() { return { expanded: false, selected: false }; }, updateState() {}, updateStateAll() {}, }, }); ``` --- --- url: /packages/vite-plugin-tsc/usage.md --- # Usage The plugin supports two main scenarios: transpiling TypeScript or just checking types. In both cases, import the plugin and add it to your Vite configuration. Use `tscArgs` to pass arguments to the TypeScript compiler and enable `prebuild` if you rely on project references. ## Transpilation Use `tsc` to emit JavaScript that Vite will bundle. Point Vite at the emitted files so all of its features continue to work: ```ts import { defineConfig } from "vite"; import { tscPlugin } from "@wroud/vite-plugin-tsc"; export default defineConfig({ root: "dist", // folder defined as tsc outDir plugins: [ tscPlugin({ tscArgs: ["-b"], prebuild: true }), // prebuild is useful for project references ], }); ``` ## Type Checking Run `tsc` in watch mode without emitting files to surface type errors while keeping esbuild's output: ```ts import { defineConfig } from "vite"; import { tscPlugin } from "@wroud/vite-plugin-tsc"; export default defineConfig({ plugins: [ tscPlugin({ tscArgs: ["--project", "tsconfig.json"], prebuild: false, enableOverlay: true, }), ], }); ``` --- --- url: /packages/vite-plugin-tsc/overview.md --- # Vite Plugin TSC `@wroud/vite-plugin-tsc` brings the TypeScript compiler (`tsc`) to Vite. Because Vite relies on esbuild for speed, TypeScript errors are not checked by default. This plugin can run `tsc` to surface type errors during development or builds and can also transpile TypeScript files, allowing Vite to be configured to consume those files without losing any of its features. It also supports TypeScript project references. ## Use Cases * **Type checking**: Run `tsc` alongside Vite's dev server to report type errors that esbuild ignores. * **Transpilation**: Transpile TypeScript files using `tsc`, then let Vite consume the generated files. ## Key Features * **Transpilation**: Uses `tsc` to transpile TypeScript files. * **Project references**: Supports TypeScript project references. * **Background type checking**: Runs `tsc` in watch mode to surface type errors without blocking the Vite dev server. * **Prebuild support**: Optionally builds dependencies before Vite starts. * **Watch mode**: Automatically recompiles when files change. * **IDE overlay**: Shows type errors in the browser overlay when `enableOverlay` is enabled. ## Examples ### Transpilation ```ts import { defineConfig } from "vite"; import { tscPlugin } from "@wroud/vite-plugin-tsc"; export default defineConfig({ root: "dist", // folder defined as tsc outDir plugins: [ tscPlugin({ tscArgs: ["-b"], prebuild: true, // recommended for TypeScript project references }), ], }); ``` ### Type Checking Only ```ts import { defineConfig } from "vite"; import { tscPlugin } from "@wroud/vite-plugin-tsc"; export default defineConfig({ plugins: [ tscPlugin({ tscArgs: ["--project", "tsconfig.json"], prebuild: false, enableOverlay: true, }), ], }); ``` --- --- url: /guide/package/di/getting-started/why-use-dependency-injection.md --- # Why Use Dependency Injection? ## Introduction In this guide, we will explore how Dependency Injection (DI) can address various challenges in software development. We will discuss the advantages of using @wroud/di and identify scenarios where DI is particularly beneficial. Through step-by-step examples, we will demonstrate the practical benefits and efficiency gains achieved by implementing DI in your applications. Learn how DI can improve your code structure, enhance testability, and manage dependencies more effectively. ## Example 1: Base Application We'll start with a simple example to understand the structure and then evolve it step-by-step. ### Initial Code ```typescript class Logger { log(message: string) { console.log(message); } } class Database { constructor(private logger: Logger) {} connect() { this.logger.log("connected"); } disconnect() { this.logger.log("disconnected"); } } class App { constructor( private logger: Logger, private database: Database, ) {} start() { this.database.connect(); this.logger.log("started"); } stop() { this.database.disconnect(); this.logger.log("stopped"); } } ``` In this basic example, we have an `App` class that depends on a `Logger` and a `Database`. The `Database` also depends on the `Logger`. This setup shows how the `App` starts and stops by connecting and disconnecting the database and logging these events. ::: tabs \== Initialization Variant 1 ```typescript const logger = new Logger();// [!code ++] const database = new Database(logger);// [!code ++] const app = new App(logger, database);// [!code ++] app.start(); app.stop(); ``` In this initialization variant, we manually create instances of `Logger` and `Database` and pass them to the `App` constructor. This approach works but can become cumbersome as the application grows. \== Initialization Variant 2 ```typescript class App { private logger: Logger;// [!code ++] private database: Database;// [!code ++] constructor() { this.logger = new Logger();// [!code ++] this.database = new Database(this.logger);// [!code ++] } start() { this.database.connect(); this.logger.log("started"); } stop() { this.database.disconnect(); this.logger.log("stopped"); } } const app = new App();// [!code ++] app.start(); app.stop(); ``` In this variant, the `App` class creates its own dependencies internally. While this approach centralizes the creation of dependencies within the `App` class, it reduces flexibility and makes it harder to manage dependencies in a larger application. \== Initialization with DI ```typescript @injectable()// [!code ++] class Logger { log(message: string) { console.log(message); } } @injectable(() => [Logger])// [!code ++] class Database { constructor(private logger: Logger) {} connect() { this.logger.log("connected"); } disconnect() { this.logger.log("disconnected"); } } @injectable(() => [Logger, Database])// [!code ++] class App { constructor( private logger: Logger, private database: Database, ) {} start() { this.database.connect(); this.logger.log("started"); } stop() { this.database.disconnect(); this.logger.log("stopped"); } } const serviceProvider = new ServiceContainerBuilder()// [!code ++] .addSingleton(Logger)// [!code ++] .addSingleton(App)// [!code ++] .addSingleton(Database)// [!code ++] .build();// [!code ++] const app = serviceProvider.getService(App);// [!code ++] app.start(); app.stop(); ``` In this variant, we use `@wroud/di` to manage the creation and injection of dependencies. We decorate our classes with `@injectable` and use `ServiceContainerBuilder` to register our services. ::: ## Example 2: Expanding the Base Application Now, let's expand our base application by adding more classes and see how the initialization changes. ### Expanded Application Code ```typescript class Logger { log(message: string) { console.log(message); } } class Database { constructor(private logger: Logger) {} connect() { this.logger.log("connected"); } disconnect() { this.logger.log("disconnected"); } query() { this.logger.log("queried"); } } class UsersManager {// [!code ++] constructor(// [!code ++] private logger: Logger,// [!code ++] private database: Database,// [!code ++] ) {}// [!code ++] addUser() {// [!code ++] this.database.query();// [!code ++] this.logger.log("added user");// [!code ++] }// [!code ++] }// [!code ++] class RegistrationService {// [!code ++] constructor(// [!code ++] private logger: Logger,// [!code ++] private usersManager: UsersManager,// [!code ++] ) {}// [!code ++] registerUser() {// [!code ++] this.usersManager.addUser();// [!code ++] this.logger.log("registered user");// [!code ++] }// [!code ++] }// [!code ++] class App { constructor( private logger: Logger, private database: Database, ) {} start() { this.database.connect(); this.logger.log("started"); } stop() { this.database.disconnect(); this.logger.log("stopped"); } } ``` In this expanded version, we added `UsersManager` and `RegistrationService` classes. These new classes also depend on `Logger` and `Database`. ::: tabs \== Initialization Variant 1 ```typescript const logger = new Logger(); const database = new Database(logger); const usersManager = new UsersManager(logger, database);// [!code ++] const registrationService = new RegistrationService(logger, usersManager);// [!code ++] const app = new App(logger, database); app.start(); registrationService.registerUser();// [!code ++] app.stop(); ``` This variant demonstrates the manual creation of instances for all new classes. As the application grows, this approach quickly becomes tedious and error-prone due to the manual wiring of dependencies. \== Initialization Variant 2 ```typescript class UsersManager { readonly registrationService: RegistrationService;// [!code ++] constructor( private logger: Logger, private database: Database, ) { this.registrationService = new RegistrationService(this.logger, this);// [!code ++] } addUser() { this.database.query(); this.logger.log("added user"); } } class Database { readonly usersManager: UsersManager;// [!code ++] constructor(private logger: Logger) { this.usersManager = new UsersManager(this.logger, this);// [!code ++] } connect() { this.logger.log("connected"); } disconnect() { this.logger.log("disconnected"); } query() { this.logger.log("queried"); } } const app = new App(); app.start(); app.database.usersManager.registrationService.registerUser();// [!code ++] app.stop(); ``` In this variant, we moved some dependency creation inside the constructors of the classes that need them. While it helps to reduce the boilerplate code at the initialization point, it still tightly couples the classes, making it difficult to change or replace dependencies later. Additionally, this creates a cyclic dependency between `UsersManager` and `RegistrationService`. \== Initialization with DI ```typescript @injectable(() => [Logger, Database])// [!code ++] class UsersManager { constructor( private logger: Logger, private database: Database, ) {} addUser() { this.database.query(); this.logger.log("added user"); } } @injectable(() => [Logger, UsersManager])// [!code ++] class RegistrationService { constructor( private logger: Logger, private usersManager: UsersManager, ) {} registerUser() { this.usersManager.addUser(); this.logger.log("registered user"); } } const serviceProvider = new ServiceContainerBuilder() .addSingleton(Logger) .addSingleton(App) .addSingleton(Database) .addSingleton(UsersManager)// [!code ++] .addSingleton(RegistrationService)// [!code ++] .build(); const app = serviceProvider.getService(App); const registrationService = serviceProvider.getService(RegistrationService);// [!code ++] app.start(); registrationService.registerUser();// [!code ++] app.stop(); ``` In this variant, we use `@wroud/di` to manage the creation and injection of dependencies. We decorate our classes with `@injectable` and register them with the `ServiceContainerBuilder`, which automatically handles their instantiation and dependency resolution. ::: ## Example 3: Separating Concerns with Independent Services Next, we will further refactor our application by separating some functionality into independent services. This helps in managing responsibilities better. ### Refactored Application Code ```typescript class Logger { log(message: string) { console.log(message); } } class DatabaseConnection {// [!code ++] constructor(private logger: Logger) {}// [!code ++] rawQuery() {// [!code ++] this.logger.log("raw queried");// [!code ++] }// [!code ++] connect() {// [!code ++] this.logger.log("connected");// [!code ++] }// [!code ++] disconnect() {// [!code ++] this.logger.log("disconnected");// [!code ++] }// [!code ++] } class Database { constructor( private logger: Logger, private connection: DatabaseConnection,// [!code ++] ) {} query() { this.connection.rawQuery();// [!code ++] this.logger.log("queried"); } disconnect() {// [!code --] this.logger.log("disconnected");// [!code --] }// [!code --] query() {// [!code --] this.logger.log("queried");// [!code --] }// [!code --] } class UsersManager { constructor( private logger: Logger, private database: Database, ) {} addUser() { this.database.query(); this.logger.log("added user"); } } class RegistrationService { constructor( private logger: Logger, private usersManager: UsersManager, ) {} registerUser() { this.usersManager.addUser(); this.logger.log("registered user"); } } class App { constructor( private logger: Logger, private database: Database,// [!code --] private connection: DatabaseConnection,// [!code ++] ) {} start() { this.database.connect();// [!code --] this.connection.connect();// [!code ++] this.logger.log("started"); } stop() { this.database.disconnect();// [!code --] this.connection.disconnect();// [!code ++] this.logger.log("stopped"); } } ``` In this refactoring, we introduced a new `DatabaseConnection` class to handle the actual connection logic. This separation of concerns makes the code more modular and easier to manage. ::: tabs \== Initialization Variant 1 ```typescript const logger = new Logger(); const databaseConnection = new DatabaseConnection(logger);// [!code ++] const database = new Database(logger);// [!code --] const database = new Database(logger, databaseConnection);// [!code ++] const usersManager = new UsersManager(logger, database); const registrationService = new RegistrationService(logger, usersManager); const app = new App(logger, database);// [!code --] const app = new App(logger, databaseConnection);// [!code ++] app.start(); registrationService.registerUser(); app.stop(); ``` This variant shows the manual creation and wiring of all instances, including the new `DatabaseConnection` class. As expected, this can get complex and error-prone as the number of dependencies grows. \== Initialization Variant 2 ```typescript class App { private logger: Logger; private connection: DatabaseConnection;// [!code ++] readonly database: Database; constructor() { this.logger = new Logger(); this.connection = new DatabaseConnection(this.logger);// [!code ++] this.database = new Database(this.logger);// [!code --] this.database = new Database(this.logger, this.connection);// [!code ++] } start() { this.connection.connect(); this.logger.log("started"); } stop() { this.connection.disconnect(); this.logger.log("stopped"); } } const app = new App(); app.start(); app.database.usersManager.registrationService.registerUser(); app.stop(); ``` In this variant, the `App` class internally creates instances of `Logger`, `DatabaseConnection`, and `Database`. This approach reduces some boilerplate but still involves manual wiring inside the constructors. However, note that the logical hierarchy `app.database.usersManager.registrationService` is problematic. This is a consequence of manual dependency management. As our code evolves, dependencies need to be moved up the hierarchy to be shared with other dependencies. This makes it difficult to maintain a clean separation of concerns and manage dependencies efficiently. \== Initialization with DI ```typescript @injectable(() => [Logger])// [!code ++] class DatabaseConnection { constructor(private logger: Logger) {} rawQuery() { this.logger.log("raw queried"); } connect() { this.logger.log("connected"); } disconnect() { this.logger.log("disconnected"); } } @injectable(() => [Logger, Database])// [!code --] @injectable(() => [Logger, DatabaseConnection])// [!code ++] class App { constructor( private logger: Logger, private connection: DatabaseConnection, ) {} start() { this.connection.connect(); this.logger.log("started"); } stop() { this.connection.disconnect(); this.logger.log("stopped"); } } @injectable(() => [Logger])// [!code --] @injectable(() => [Logger, DatabaseConnection])// [!code ++] class Database { constructor( private logger: Logger, private connection: DatabaseConnection, ) {} query() { this.connection.rawQuery(); this.logger.log("queried"); } } const serviceProvider = new ServiceContainerBuilder() .addSingleton(Logger) .addSingleton(DatabaseConnection)// [!code ++] .addSingleton(App) .addSingleton(Database) .addSingleton(UsersManager) .addSingleton(RegistrationService) .build(); const app = serviceProvider.getService(App); const registrationService = serviceProvider.getService(RegistrationService); app.start(); registrationService.registerUser(); app.stop(); ``` In this variant, we use `@wroud/di` to manage the creation and injection of dependencies. Note that we only made a few changes to the code to use dependency injection, without affecting the logic and without increasing complexity. ::: ### Explanation 1. **Defining Services**: Each class is decorated with `@injectable()`, making them injectable services. 2. **Service Container Builder**: We create a `ServiceContainerBuilder` and register each service with `addSingleton`. 3. **Resolving Dependencies**: The `IServiceProvider` automatically handles the creation and injection of dependencies. 4. **Starting the Application**: The `App` class is resolved from the container, and its dependencies are injected automatically. ### Benefits of Using Dependency Injection * **Simplicity**: Dependencies are declared and managed in one place, reducing boilerplate code. * **Flexibility**: Easily swap implementations of services without changing the dependent code. * **Testability**: Mock dependencies can be injected for testing purposes, improving testability. * **Maintainability**: As the project grows, managing dependencies remains straightforward and less error-prone. ### Summary Through these examples, we've illustrated the progression from manual dependency management to using a dependency injection system. By leveraging `@wroud/di`, we achieve a cleaner, more maintainable, and flexible codebase. Dependency Injection simplifies the initialization and wiring of services, allowing developers to focus on the core logic of their applications.