### Quick Start: Basic Monopole Application Source: https://github.com/denostack/monopole/blob/main/README.md Demonstrates setting up a basic application with services, a module, and a container. Ensure 'monopole' is installed. ```typescript import { createContainer, inject, type Module } from "monopole"; // Define services class Logger { log(message: string) { console.log(`[LOG] ${message}`); } } class UserService { @inject(Logger) logger!: Logger; getUser(id: string) { this.logger.log(`Fetching user ${id}`); return { id, name: "John Doe" }; } } // Create a module const appModule: Module = { providers: [ Logger, UserService, ], exports: [UserService], }; // Create and use container const container = await createContainer(appModule); const userService = container.get(UserService); userService.getUser("123"); ``` -------------------------------- ### Monopole Quick Start Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/00-START-HERE.md Demonstrates how to set up a basic Monopole container with a Logger and UserService, including property injection and container usage. ```typescript import { createContainer, inject, type Module } from "@denostack/monopole"; class Logger { log(message: string) { console.log(`[LOG] ${message}`); } } class UserService { @inject(Logger) logger!: Logger; getUser(id: string) { this.logger.log(`Getting user ${id}`); return { id, name: "John Doe" }; } } const appModule: Module = { providers: [Logger, UserService], exports: [UserService], }; const container = await createContainer(appModule); const userService = container.get(UserService); userService.getUser("123"); ``` -------------------------------- ### Import Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/api-signatures.md Demonstrates how to import core components from the main package. ```typescript import { createContainer, Container, Module, inject, UndefinedError, ServiceIdentifier, Provider, ClassProvider, ValueProvider, FactoryProvider, ExistingProvider, ConstructType, AbstractType, MaybePromise, Thunk, MaybeThunk, } from "@denostack/monopole"; ``` -------------------------------- ### Configuration Management Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Shows how to manage application configuration using Monopole. This example uses environment variables and a configuration service. ```typescript import { Injectable, Provider, Inject } from "@monopole/core"; interface AppConfig { apiEndpoint: string; timeout: number; } @Injectable() export class ConfigService { private config: AppConfig; constructor() { this.config = { apiEndpoint: process.env.API_ENDPOINT || "http://localhost:8080", timeout: parseInt(process.env.TIMEOUT || "5000", 10) }; } get(key: K): AppConfig[K] { return this.config[key]; } } // Provider for the configuration service const configServiceProvider: Provider = { provide: ConfigService, useClass: ConfigService }; // Usage: // @Injectable() // export class ApiClient { // constructor(private config: ConfigService) {} // fetchData() { // const url = this.config.get('apiEndpoint'); // // ... fetch data using url ... // } // } ``` -------------------------------- ### Direct Class Registration Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates how to register a class directly as a provider. This is a fundamental pattern for dependency injection. ```typescript import { Injectable } from "@monopole/core"; @Injectable() export class MyService { getData(): string { return "Hello from MyService!"; } } // Usage in another service or module: // @Injectable() // export class AnotherService { // constructor(private myService: MyService) {} // process() { // console.log(this.myService.getData()); // } // } ``` -------------------------------- ### FactoryProvider Example with Dependencies Source: https://github.com/denostack/monopole/blob/main/_autodocs/types.md Illustrates how to use FactoryProvider to create services with dependencies, including asynchronous initialization and optional dependencies. ```typescript const module: Module = { providers: [ { id: "apiUrl", useValue: "https://api.example.com" }, { id: HttpClient, useFactory: (apiUrl: string) => new HttpClient(apiUrl), inject: ["apiUrl"], }, { id: Database, useFactory: async (config) => { const db = new Database(config); await db.connect(); return db; }, inject: ["dbConfig"], }, { id: Logger, useFactory: (cache) => new Logger(cache ?? null), inject: [[CacheService, true]], // CacheService is optional }, ], }; ``` -------------------------------- ### Multi-Tenant Architecture Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Shows a pattern for implementing multi-tenancy, where different tenants might have their own configurations or data scopes. This example uses a tenant identifier to resolve tenant-specific services. ```typescript import { Injectable, Provider, Inject, Container } from "@monopole/core"; interface TenantConfig { dbConnectionString: string; } // A factory that provides tenant-specific configuration const tenantConfigFactory: Provider = { provide: "TENANT_CONFIG", useFactory: (tenantId: string) => { console.log(`Resolving config for tenant: ${tenantId}`); // In a real app, fetch config from DB or file based on tenantId if (tenantId === "tenant-a") { return { dbConnectionString: "postgres://userA:passA@hostA/dbA" }; } else { return { dbConnectionString: "postgres://userB:passB@hostB/dbB" }; } }, // The tenantId would typically come from a request context or similar inject: ["REQUEST_TENANT_ID"] }; @Injectable() export class TenantService { constructor(@Inject("TENANT_CONFIG") private config: TenantConfig) {} getDbConnection(): string { return this.config.dbConnectionString; } } // Usage: // async function runForTenant(tenantId: string) { // const container = new Container(); // container.register({ provide: "REQUEST_TENANT_ID", useValue: tenantId }); // container.register(tenantConfigFactory); // container.register(TenantService); // const tenantService = await container.resolve(TenantService); // console.log(`Tenant ${tenantId} DB: ${tenantService.getDbConnection()}`); // } // // runForTenant("tenant-a"); // runForTenant("tenant-b"); ``` -------------------------------- ### Install Monopole with Deno Source: https://github.com/denostack/monopole/blob/main/README.md Command to add the Monopole package to your Deno project. This is the recommended installation method for Deno runtimes. ```bash deno add @denostack/monopole ``` -------------------------------- ### Value Provider Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Illustrates how to use a simple value as a provider. Useful for constants or pre-defined configurations. ```typescript import { Provider } from "@monopole/core"; const myValueProvider: Provider = { provide: "MY_CONSTANT", useValue: "This is a constant value." }; // Usage: // @Injectable() // export class ConsumerService { // constructor(@Inject("MY_CONSTANT") private constant: string) {} // logConstant() { // console.log(this.constant); // } // } ``` -------------------------------- ### Core Concepts: Existing Provider (Alias) Example Source: https://github.com/denostack/monopole/blob/main/README.md Shows how to create an alias for an existing provider using 'useExisting'. ```typescript providers: [ { id: Logger, useClass: ConsoleLogger }, { id: "logger", useExisting: Logger }, ]; ``` -------------------------------- ### Core Concepts: Class Provider Example Source: https://github.com/denostack/monopole/blob/main/README.md Shows how to register a service using a class provider, either directly or with an explicit ID. ```typescript // Direct class registration providers: [MyService]; // With explicit ID providers: [{ id: "myService", useClass: MyService, }]; ``` -------------------------------- ### Service Introspection Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates how to introspect the dependency injection container to get information about registered services. This can be useful for debugging or building management tools. ```typescript import { Container, Injectable } from "@monopole/core"; @Injectable() class ServiceX { } @Injectable() class ServiceY { constructor(private serviceX: ServiceX) {} } async function inspectContainer() { const container = new Container(); container.register(ServiceX); container.register(ServiceY); // Get metadata about registered services const registrations = container.getRegistrations(); console.log("Registered Services:"); registrations.forEach(reg => { console.log(`- Token: ${reg.token.toString()}, Type: ${reg.useClass?.name || 'N/A'}, Scope: ${reg.scope}`); // In a more advanced scenario, you could inspect dependencies // console.log(` Dependencies: ${reg.dependencies?.map(d => d.token.toString()).join(', ') || 'None'}`); }); } inspectContainer(); ``` -------------------------------- ### Dynamic Service Registration Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Shows how services can be registered dynamically at runtime, rather than being statically defined. This is useful for scenarios where the available services are not known at compile time. ```typescript import { Container, Injectable } from "@monopole/core"; // Assume some services are defined elsewhere class ServiceA { id = 'A'; } class ServiceB { id = 'B'; } async function bootstrap(serviceClasses: any[]) { const container = new Container(); for (const ServiceClass of serviceClasses) { // Dynamically register each service class container.register(ServiceClass); console.log(`Registered service: ${ServiceClass.name}`); } // Resolve and use services const serviceA = await container.resolve(ServiceA); console.log(`Resolved Service A: ${serviceA.id}`); const serviceB = await container.resolve(ServiceB); console.log(`Resolved Service B: ${serviceB.id}`); } // Example usage with an array of service classes bootstrap([ServiceA, ServiceB]); ``` -------------------------------- ### Install Monopole with npm Source: https://github.com/denostack/monopole/blob/main/README.md Command to install the Monopole package using npm for Node.js and browser environments. This is used after transpiling the code. ```bash npm install monopole ``` -------------------------------- ### Environment-Based Configuration Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates how to load configuration values based on the current environment (e.g., development, staging, production). This ensures appropriate settings are used for different deployment stages. ```typescript import { Provider, Injectable, Inject } from "@monopole/core"; interface EnvironmentConfig { logLevel: "debug" | "info" | "warn" | "error"; databaseUrl: string; } const environmentConfig: Record> = { development: { logLevel: "debug", databaseUrl: "sqlite::memory:" }, production: { logLevel: "warn", databaseUrl: process.env.DATABASE_URL || "postgres://prod_user:pass@host/db" } }; @Injectable() export class EnvConfigService { private config: EnvironmentConfig; constructor() { const env = process.env.NODE_ENV || "development"; const defaults = environmentConfig.development as EnvironmentConfig; const envSpecific = environmentConfig[env] as Partial; this.config = { ...defaults, ...envSpecific, // Ensure required fields are always present logLevel: envSpecific.logLevel || defaults.logLevel, databaseUrl: envSpecific.databaseUrl || defaults.databaseUrl }; } get(key: K): EnvironmentConfig[K] { return this.config[key]; } } // Provider for the environment configuration service const envConfigServiceProvider: Provider = { provide: EnvConfigService, useClass: EnvConfigService }; // Usage: // const configService = new EnvConfigService(); // Or resolve from container // console.log(`Log level: ${configService.get('logLevel')}`); // console.log(`DB URL: ${configService.get('databaseUrl')}`); ``` -------------------------------- ### Using ConstructType for Service Registration Source: https://github.com/denostack/monopole/blob/main/_autodocs/types.md Shows an example of defining a UserService class and then registering and retrieving it using its constructor as the service identifier. ```typescript class UserService { getUser(id: string) { return { id, name: "John" }; } } const container = await createContainer({ providers: [UserService], exports: [UserService], }); const userService = container.get(UserService); ``` -------------------------------- ### Creating a Container with Module Source: https://github.com/denostack/monopole/blob/main/_autodocs/README.md Basic example of creating a DI container from a module definition and retrieving a service. Requires importing `createContainer` and `Module`. ```typescript import { createContainer, type Module } from "@denostack/monopole"; const appModule: Module = { providers: [Logger, UserService], exports: [UserService], }; const container = await createContainer(appModule); const service = container.get(UserService); ``` -------------------------------- ### ExistingProvider Example for Aliasing Services Source: https://github.com/denostack/monopole/blob/main/_autodocs/types.md Shows how to use ExistingProvider to create aliases for existing services, allowing multiple identifiers to resolve to the same instance. ```typescript const module: Module = { providers: [ ConsoleLogger, // Registered as ConsoleLogger { id: Logger, useExisting: ConsoleLogger }, // Alias: Logger points to ConsoleLogger { id: "logger", useExisting: Logger }, // Alias: "logger" points to Logger ], exports: [Logger, "logger"], }; const container = await createContainer(module); const logger1 = container.get(Logger); const logger2 = container.get(ConsoleLogger); const logger3 = container.get("logger"); console.log(logger1 === logger2); // true console.log(logger1 === logger3); // true ``` -------------------------------- ### HTTP Server with Modular Architecture Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt An example of setting up an HTTP server using a modular architecture. This demonstrates how Monopole can be used to structure larger applications. ```typescript // Assume relevant imports from @monopole/http and @monopole/core // Define modules for different parts of the server (e.g., controllers, services) // Example Controller Module @Module({ controllers: [UserController, ProductController], providers: [UserService, ProductService], exports: [UserService] }) export class ApiModule {} // Main Application Setup async function bootstrap() { const container = new Container(); container.register(ApiModule); // ... other configurations ... const server = await container.resolve(HttpServer); server.listen(3000, () => { console.log('Server listening on port 3000'); }); } bootstrap(); ``` -------------------------------- ### Core Concepts: Value Provider Examples Source: https://github.com/denostack/monopole/blob/main/README.md Demonstrates registering primitive values or promises as providers using 'useValue'. ```typescript providers: [ { id: "apiUrl", useValue: "https://api.example.com" }, { id: "config", useValue: Promise.resolve({ key: "value" }) }, ]; ``` -------------------------------- ### ClassProvider Examples Source: https://github.com/denostack/monopole/blob/main/_autodocs/types.md Illustrates using ClassProvider to register services with an explicit string ID or to provide an implementation for an abstract base class. ```typescript // Explicit ID const module: Module = { providers: [ { id: "logger", useClass: ConsoleLogger }, ], }; // Implementation inheritance abstract class BaseService {} class ConcreteService extends BaseService {} const module: Module = { providers: [ { id: BaseService, useClass: ConcreteService }, ], }; ``` -------------------------------- ### Module Composition Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Illustrates how to compose multiple modules together to create a larger application structure. This promotes modularity and reusability. ```typescript import { Module } from "@monopole/core"; import { ServiceA } from "./service-a"; import { ServiceB } from "./service-b"; @Module({ providers: [ServiceA, ServiceB], exports: [ServiceA] }) export class SharedModule {} @Module({ imports: [SharedModule], providers: [/* ... */], exports: [/* ... */] }) export class AppModule {} ``` -------------------------------- ### Property Injection Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/README.md Demonstrates how to use the @inject decorator to populate class properties with dependencies. ```typescript class UserService { @inject(Logger) logger!: Logger; } ``` -------------------------------- ### Plugin System with Monopole Source: https://github.com/denostack/monopole/blob/main/_autodocs/integration-examples.md Illustrates creating an extensible plugin system using Monopole's dependency injection. This example shows how to define plugin interfaces, register plugins, and dynamically load them into the container. ```typescript import { createContainer, inject, type Module, type ServiceIdentifier } from "@denostack/monopole"; interface Plugin { name: string; version: string; providers: any[]; exports: ServiceIdentifier[]; boot?(container: any): Promise; dispose?(container: any): Promise; } class Logger { log(message: string) { console.log(`[LOG] ${message}`); } } class PluginRegistry { private plugins: Plugin[] = []; register(plugin: Plugin) { this.plugins.push(plugin); console.log(`Plugin registered: ${plugin.name} v${plugin.version}`); } getPlugins(): Plugin[] { return this.plugins; } } class AuthPlugin implements Plugin { name = "auth"; version = "1.0.0"; providers = [ { id: "authService", useValue: { authenticate: () => true }, }, ]; exports = ["authService"]; } class CachingPlugin implements Plugin { name = "caching"; version = "1.0.0"; providers = [ { id: "cacheService", useValue: { get: (key: string) => null, set: (key: string, value: any) => {} }, }, ]; exports = ["cacheService"]; } async function createPluginModule(plugins: Plugin[]): Promise { const allProviders: any[] = []; const allExports: ServiceIdentifier[] = []; const bootHooks: ((container: any) => Promise)[] = []; const disposeHooks: ((container: any) => Promise)[] = []; for (const plugin of plugins) { allProviders.push(...plugin.providers); allExports.push(...plugin.exports); if (plugin.boot) bootHooks.push(plugin.boot); if (plugin.dispose) disposeHooks.push(plugin.dispose); } return { providers: [ Logger, { id: PluginRegistry, useFactory: () => { const registry = new PluginRegistry(); for (const plugin of plugins) { registry.register(plugin); } return registry; }, }, ...allProviders, ], exports: [Logger, PluginRegistry, ...allExports], boot: async (container) => { for (const hook of bootHooks) { await hook(container); } }, dispose: async (container) => { for (const hook of disposeHooks) { await hook(container); } }, }; } // Usage async function main() { const plugins: Plugin[] = [ new AuthPlugin(), new CachingPlugin(), ]; const pluginModule = await createPluginModule(plugins); await using container = await createContainer(pluginModule); const registry = container.get(PluginRegistry); const logger = container.get(Logger); logger.log(`Loaded ${registry.getPlugins().length} plugins`); for (const plugin of registry.getPlugins()) { logger.log(`- ${plugin.name} v${plugin.version}`); } const authService = container.get("authService"); console.log("Auth service available:", !!authService); } main().catch(console.error); ``` -------------------------------- ### Monopole File Dependencies Source: https://github.com/denostack/monopole/blob/main/_autodocs/MANIFEST.md Illustrates the hierarchical structure and dependencies between Monopole's documentation files, starting from the entry point. ```text 00-START-HERE.md (entry point) ├── INDEX.md (complete navigation) │ ├── API documentation │ │ ├── api-signatures.md │ │ ├── create_container.md │ │ ├── container.md │ │ ├── module.md │ │ ├── inject.md │ │ ├── types.md │ │ └── errors.md │ └── Usage guides │ ├── provider-patterns.md │ ├── advanced-usage.md │ └── integration-examples.md ├── README.md └── api-signatures.md ``` -------------------------------- ### Error Handling Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates how to handle errors within the dependency injection system, such as undefined providers or resolution failures. ```typescript import { Container } from "@monopole/core"; import { UndefinedError } from "@monopole/errors"; async function bootstrap() { const container = new Container(); // Attempting to resolve a service that hasn't been registered try { await container.resolve("NonExistentService"); } catch (error) { if (error instanceof UndefinedError) { console.error("Caught expected error:", error.message); console.error("Resolution stack:", error.stackTrace); } else { throw error; } } } bootstrap(); ``` -------------------------------- ### get Source: https://github.com/denostack/monopole/blob/main/_autodocs/container.md Retrieves a resolved service instance from the container. Returns the same instance on subsequent calls. Services are resolved during container creation, so get() returns immediately. ```APIDOC ## get ### Description Retrieves a resolved service instance from the container. ### Method ```typescript get(id: ServiceIdentifier): T ``` ### Parameters #### Path Parameters - **id** (`ServiceIdentifier`) - Required - The service identifier (class, string, or symbol) ### Return Type `T` — The resolved service instance ### Throws `UndefinedError` — If the service is not registered or not exported ### Description Returns the resolved and cached instance of the service. The same instance is returned on subsequent calls. Services are resolved during container creation, so `get()` returns immediately (synchronous). ### Usage Example: ```typescript const container = await createContainer(appModule); const userService = container.get(UserService); const apiUrl = container.get("apiUrl"); ``` ``` -------------------------------- ### Import createContainer in Deno Source: https://github.com/denostack/monopole/blob/main/README.md Example of importing the createContainer function from the Monopole package in a Deno project. ```typescript import { createContainer } from "@denostack/monopole"; ``` -------------------------------- ### Factory Provider Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Shows how to create a provider using a factory function. This allows for dynamic value generation or complex object creation. ```typescript import { Provider } from "@monopole/core"; interface DynamicData { timestamp: number; message: string; } const dynamicDataProvider: Provider = { provide: "DYNAMIC_DATA", useFactory: () => { return { timestamp: Date.now(), message: "Data generated dynamically." }; } }; // Usage: // @Injectable() // export class DataDisplayService { // constructor(@Inject("DYNAMIC_DATA") private data: DynamicData) {} // display() { // console.log(`Timestamp: ${this.data.timestamp}, Message: ${this.data.message}`); // } // } ``` -------------------------------- ### Database Connection Pooling Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates integrating database connection pooling within a Monopole application. This is crucial for performance in database-intensive applications. ```typescript import { Provider, Injectable, Container } from "@monopole/core"; import { Pool, PoolClient } from "pg"; // Example using 'pg' library // Provider for the database connection pool const dbPoolProvider: Provider = { provide: "DB_POOL", useFactory: () => { const pool = new Pool({ user: "dbuser", host: "localhost", database: "mydb", password: "secret", port: 5432, }); return pool; } }; // Service that uses the connection pool @Injectable() export class DataService { constructor(@Inject("DB_POOL") private pool: Pool) {} async query(sql: string, params: any[]): Promise { const client: PoolClient = await this.pool.connect(); try { const result = await client.query(sql, params); return result.rows; } finally { client.release(); } } } // Usage in application setup: // const container = new Container(); // container.register(dbPoolProvider); // container.register(DataService); ``` -------------------------------- ### Service Resolution Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Shows the basic process of resolving a service from the dependency injection container. This is the core mechanism for obtaining service instances. ```typescript import { Container } from "@monopole/core"; import { MyService } from "./my-service"; async function bootstrap() { const container = new Container(); container.register(MyService); const myServiceInstance = await container.resolve(MyService); console.log(myServiceInstance.getData()); } bootstrap(); ``` -------------------------------- ### Core Concepts: Factory Provider Example Source: https://github.com/denostack/monopole/blob/main/README.md Illustrates creating a provider using a factory function that depends on other registered providers. ```typescript providers: [{ id: HttpClient, useFactory: (apiUrl: string) => new HttpClient(apiUrl), inject: ["apiUrl"], }]; ``` -------------------------------- ### Resource Pooling Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates the concept of resource pooling, where a set of expensive resources (like database connections or file handles) are created upfront and reused. This improves performance by avoiding repeated creation and destruction costs. ```typescript import { Provider, Injectable, Inject } from "@monopole/core"; interface Resource { id: number; release: () => void; } class ResourceManager { private pool: Resource[] = []; private nextId = 1; constructor(private poolSize: number) { console.log(`Initializing resource pool with size ${poolSize}`); for (let i = 0; i < poolSize; i++) { this.createResource(); } } private createResource(): void { const resource: Resource = { id: this.nextId++, release: () => { console.log(`Resource ${resource.id} released back to pool.`); this.pool.push(resource); } }; this.pool.push(resource); console.log(`Created resource ${resource.id}`); } acquire(): Resource { if (this.pool.length === 0) { throw new Error("Pool exhausted. Cannot acquire resource."); } const resource = this.pool.pop()!; console.log(`Acquired resource ${resource.id}`); return resource; } getPoolSize(): number { return this.pool.length; } } // Provider for the resource manager const resourceManagerProvider: Provider = { provide: ResourceManager, useFactory: () => new ResourceManager(5) // Pool size of 5 }; // Usage: // const container = new Container(); // container.register(resourceManagerProvider); // // async function useResource() { // const manager = await container.resolve(ResourceManager); // const resource1 = manager.acquire(); // const resource2 = manager.acquire(); // console.log(`Pool size after acquiring 2: ${manager.getPoolSize()}`); // resource1.release(); // resource2.release(); // console.log(`Pool size after releasing 2: ${manager.getPoolSize()}`); // } // // useResource(); ``` -------------------------------- ### HTTP Server with Module Architecture Source: https://github.com/denostack/monopole/blob/main/_autodocs/integration-examples.md Demonstrates building a complete HTTP server with a modular organization using Monopole. This example includes services, modules, and startup logic for a typical web application. ```typescript import { createContainer, inject, type Module } from "@denostack/monopole"; // Services class Logger { log(message: string) { console.log(`[LOG] ${message}`); } } class Config { constructor( readonly port: number = 3000, readonly host: string = "localhost", ) {} } class Database { async connect() { console.log("Database connected"); } async disconnect() { console.log("Database disconnected"); } } class UserRepository { @inject(Database) db!: Database; getUser(id: string) { return { id, name: "John Doe" }; } } class UserService { @inject(UserRepository) repository!: UserRepository; @inject(Logger) logger!: Logger; getUser(id: string) { this.logger.log(`Getting user ${id}`); return this.repository.getUser(id); } } class ApiHandler { @inject(UserService) userService!: UserService; @inject(Logger) logger!: Logger; handleGetUser(request: any) { const id = request.params.id; this.logger.log(`HTTP request: GET /users/${id}`); return this.userService.getUser(id); } } // Modules const loggerModule: Module = { providers: [Logger], exports: [Logger], }; const configModule: Module = { providers: [ { id: Config, useValue: new Config(3000, "localhost") }, ], exports: [Config], }; const databaseModule: Module = { imports: [loggerModule], providers: [Database], exports: [Database], async boot(container) { const db = container.get(Database); await db.connect(); }, async dispose(container) { const db = container.get(Database); await db.disconnect(); }, }; const dataAccessModule: Module = { imports: [databaseModule], providers: [UserRepository], exports: [UserRepository], }; const businessLogicModule: Module = { imports: [dataAccessModule, loggerModule], providers: [UserService], exports: [UserService], }; const apiModule: Module = { imports: [businessLogicModule], providers: [ApiHandler], exports: [ApiHandler], }; // Root module const appModule: Module = { imports: [apiModule, configModule], providers: [], exports: [ApiHandler, Config], }; // Startup async function main() { await using container = await createContainer(appModule); const handler = container.get(ApiHandler); const config = container.get(Config); // Simulate HTTP request const response = handler.handleGetUser({ params: { id: "123" }, }); console.log("Response:", response); } main().catch(console.error); ``` -------------------------------- ### Decorator Stacking Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates how multiple decorators can be applied to a class or method. This allows for combining different functionalities, such as logging and authorization. ```typescript import { Injectable, MethodDecorator, ClassDecorator, applyDecorators } from "@monopole/core"; // Example Decorator 1: Logging function Loggable(target: any, propertyKey?: string | symbol) { console.log(`@Loggable applied to ${propertyKey || target.name}`); // In a real scenario, this would add logging logic } // Example Decorator 2: Caching function Cacheable(target: any, propertyKey?: string | symbol) { console.log(`@Cacheable applied to ${propertyKey || target.name}`); // In a real scenario, this would add caching logic } // Applying multiple decorators using applyDecorators @Injectable() @applyDecorators( Loggable, // Class decorator Cacheable // Class decorator ) export class DataService { @Loggable @Cacheable getData(id: string): string { console.log(`Fetching data for id: ${id}`); return `Data for ${id}`; } } // Usage: // const service = new DataService(); // service.getData("123"); ``` -------------------------------- ### Monopole Service Resolution Source: https://github.com/denostack/monopole/blob/main/_autodocs/00-START-HERE.md Demonstrates how to create a Monopole container and retrieve service instances using the `get` method. ```typescript const container = await createContainer(appModule); const logger = container.get(Logger); const url = container.get("apiUrl"); ``` -------------------------------- ### Multi-Tenant Architecture Setup Source: https://github.com/denostack/monopole/blob/main/_autodocs/advanced-usage.md Create isolated containers per tenant with shared services. This pattern is useful for building multi-tenant applications where each tenant requires its own isolated context. ```typescript class TenantContext { constructor(readonly tenantId: string) {} } class TenantService { getTenantData(tenantId: string) { // Fetch data for tenant return { tenantId, data: [] }; } } async function createTenantContainer(tenantId: string): Promise { return createContainer({ providers: [ { id: TenantContext, useValue: new TenantContext(tenantId) }, TenantService, ], exports: [TenantService, TenantContext], }); } const tenant1Container = await createTenantContainer("tenant-1"); const tenant2Container = await createTenantContainer("tenant-2"); const service1 = tenant1Container.get(TenantService); const service2 = tenant2Container.get(TenantService); // Each service operates in its own context ``` -------------------------------- ### Basic Module with Class Providers Source: https://github.com/denostack/monopole/blob/main/_autodocs/module.md Example of creating a module that registers class providers and exports a specific service. Ensure the exported service is listed in the 'exports' array to be accessible by other modules. ```typescript class Logger { log(message: string) { console.log(`[LOG] ${message}`); } } class UserService { @inject(Logger) logger!: Logger; } const userModule: Module = { providers: [Logger, UserService], exports: [UserService], // Only UserService is visible to other modules }; ``` -------------------------------- ### MaybePromise Examples for Sync and Async Values Source: https://github.com/denostack/monopole/blob/main/_autodocs/types.md Demonstrates the use of MaybePromise for both synchronous and asynchronous values in 'useValue' and 'useFactory' providers. ```typescript // Synchronous value provider { id: "config", useValue: { key: "value" } } ``` ```typescript // Async value provider { id: "config", useValue: Promise.resolve({ key: "value" }) } ``` ```typescript // Synchronous factory { id: Service, useFactory: () => new Service() } ``` ```typescript // Async factory { id: Service, useFactory: async () => { const service = new Service(); await service.initialize(); return service; }} ``` -------------------------------- ### Lazy Evaluation with Thunks Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Demonstrates lazy evaluation of dependencies using thunks. This can improve startup performance by deferring the creation of expensive objects until they are actually needed. ```typescript import { Injectable, Provider, Inject } from "@monopole/core"; class ExpensiveService { constructor() { console.log("ExpensiveService initialized!"); // Simulate a costly initialization for (let i = 0; i < 1e7; i++) {} } doWork(): string { return "Work done"; } } // Provider using a thunk for lazy initialization const lazyExpensiveServiceProvider: Provider = { provide: "EXPENSIVE_SERVICE", useFactory: () => { // The ExpensiveService is only instantiated when this factory is called return new ExpensiveService(); } }; @Injectable() export class Consumer { constructor(@Inject("EXPENSIVE_SERVICE") private expensiveService: ExpensiveService) {} useService() { console.log(this.expensiveService.doWork()); } } // Usage: // const container = new Container(); // container.register(lazyExpensiveServiceProvider); // container.register(Consumer); // // ExpensiveService is NOT initialized yet // const consumerInstance = await container.resolve(Consumer); // // Now, when consumerInstance.useService() is called, ExpensiveService is initialized. ``` -------------------------------- ### Import createContainer in Node.js/Browser Source: https://github.com/denostack/monopole/blob/main/README.md Example of importing the createContainer function from the Monopole package in a Node.js or browser project after transpilation. ```typescript import { createContainer } from "monopole"; ``` -------------------------------- ### Testing with Monopole Mocking Source: https://github.com/denostack/monopole/blob/main/_autodocs/integration-examples.md Demonstrates how to use Monopole for testing by providing mock implementations of dependencies. This example shows setting up a test container with mocked repositories and services. ```typescript import { assertEquals } from "@std/assert"; import { createContainer, type Module } from "@denostack/monopole"; // Application code class UserService { constructor(private repository: UserRepository) {} getUser(id: string) { return this.repository.findById(id); } } class UserRepository { findById(id: string) { // Real database call throw new Error("Not implemented"); } } // Mock implementation class MockUserRepository { findById(id: string) { return { id, name: "Mock User" }; } } // Test setup async function createTestContainer(): Promise { return createContainer({ providers: [ { id: UserRepository, useClass: MockUserRepository }, { id: UserService, useFactory: (repo) => new UserService(repo), inject: [UserRepository], }, ], exports: [UserService], }); } Deno.test("UserService.getUser returns user", async () => { const container = await createTestContainer(); const service = container.get(UserService); const user = service.getUser("123"); assertEquals(user.id, "123"); assertEquals(user.name, "Mock User"); }); // Parameterized test async function runWithFixture( test: (container: any) => Promise, ) { const container = await createTestContainer(); try { return await test(container); } finally { await container.dispose(); } } Deno.test("UserService integration test", async () => { await runWithFixture(async (container) => { const service = container.get(UserService); const user = service.getUser("456"); assertEquals(user.name, "Mock User"); }); }); ``` -------------------------------- ### Database Connection Pool Management Source: https://github.com/denostack/monopole/blob/main/_autodocs/integration-examples.md Illustrates managing database connections using a resource pool with Monopole. This example defines a `DatabasePool` and shows how to acquire and release connections. ```typescript import { createContainer, type Module } from "@denostack/monopole"; interface DatabaseConnection { query(sql: string, params: any[]): Promise; close(): Promise; } class MockDatabaseConnection implements DatabaseConnection { async query(sql: string, params: any[]): Promise { return { rows: [] }; } async close(): Promise {} } class DatabasePool { private connections: DatabaseConnection[] = []; private available: DatabaseConnection[] = []; constructor(size: number) { for (let i = 0; i < size; i++) { const conn = new MockDatabaseConnection(); this.connections.push(conn); this.available.push(conn); } } acquire(): DatabaseConnection { if (this.available.length === 0) { throw new Error("No connections available"); } return this.available.pop()!; } release(conn: DatabaseConnection) { this.available.push(conn); } async dispose() { for (const conn of this.connections) { await conn.close(); } } } const databaseModule: Module = { providers: [ { id: DatabasePool, useFactory: () => new DatabasePool(5), }, ], exports: [DatabasePool], async dispose(container) { const pool = container.get(DatabasePool); await pool.dispose(); }, }; const appModule: Module = { imports: [databaseModule], providers: [], exports: [DatabasePool], }; // Usage async function example() { await using container = await createContainer(appModule); const pool = container.get(DatabasePool); const conn1 = pool.acquire(); const result = await conn1.query("SELECT * FROM users", []); pool.release(conn1); console.log("Query result:", result); } ``` -------------------------------- ### Factory Chaining Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Illustrates how to chain factory providers together, where the output of one factory can be used as input for another. This enables complex, multi-step object creation. ```typescript import { Provider, Inject } from "@monopole/core"; interface IntermediateData { value: number; } interface FinalResult { processedValue: number; } const intermediateFactory: Provider = { provide: "INTERMEDIATE_DATA", useFactory: () => { console.log("Creating intermediate data..."); return { value: 10 }; } }; const finalFactory: Provider = { provide: "FINAL_RESULT", useFactory: (intermediate: IntermediateData) => { console.log("Creating final result..."); return { processedValue: intermediate.value * 2 }; }, // Inject the output of intermediateFactory as an argument inject: ["INTERMEDIATE_DATA"] }; // Usage: // const container = new Container(); // container.register(intermediateFactory); // container.register(finalFactory); // const result = await container.resolve("FINAL_RESULT"); // console.log(result); // { processedValue: 20 } ``` -------------------------------- ### Module Lifecycle Hooks (Boot and Dispose) Source: https://github.com/denostack/monopole/blob/main/_autodocs/module.md Provides an example of implementing both 'boot' and 'dispose' lifecycle hooks within a module. The 'boot' hook is for initialization tasks, and the 'dispose' hook is for cleanup operations when the container is no longer needed. ```typescript const lifecycleModule: Module = { providers: [Service], exports: [Service], boot(container) { // Called after all services are initialized const service = container.get(Service); console.log("Module initialized"); return service.initialize?.(); }, dispose(container) { // Called when container is disposed const service = container.get(Service); console.log("Module disposing"); return service.cleanup?.(); }, }; ``` -------------------------------- ### Middleware Chains Example Source: https://github.com/denostack/monopole/blob/main/_autodocs/COMPLETION-SUMMARY.txt Illustrates how to build and execute middleware chains, a common pattern in web frameworks for handling requests sequentially. Each middleware can modify the request or response, or terminate the chain. ```typescript import { Injectable, Container } from "@monopole/core"; // Define a middleware function type type Middleware = (context: T, next: () => Promise) => Promise; // Example Middlewares const loggingMiddleware: Middleware<{ req: string, res: string }> = async (context, next) => { console.log(`[LOG] Entering middleware. Request: ${context.req}`); await next(); console.log(`[LOG] Exiting middleware. Response: ${context.res}`); }; const authMiddleware: Middleware<{ req: string, res: string }> = async (context, next) => { console.log("Authenticating..."); // Simulate auth check if (context.req === "unauthorized") { context.res = "Access Denied"; return; // Terminate chain } await next(); }; // Function to compose and run middleware async function runMiddlewareChain(context: T, middlewares: Middleware[]): Promise { let index = 0; const next = async (): Promise => { if (index < middlewares.length) { const currentMiddleware = middlewares[index++]; await currentMiddleware(context, next); } }; await next(); } // Usage: async function main() { const context1 = { req: "valid request", res: "" }; await runMiddlewareChain(context1, [loggingMiddleware, authMiddleware]); console.log("Final Response 1:", context1.res); // Should be empty or processed response const context2 = { req: "unauthorized", res: "" }; await runMiddlewareChain(context2, [loggingMiddleware, authMiddleware]); console.log("Final Response 2:", context2.res); // Should be 'Access Denied' } main(); ``` -------------------------------- ### Module Imports with createContainer Source: https://github.com/denostack/monopole/blob/main/_autodocs/create_container.md Illustrates how to use module imports to structure your application. This example shows a database module being imported into the main application module, allowing for shared dependencies and configurations. ```typescript const databaseModule: Module = { providers: [ { id: "dbConfig", useValue: { host: "localhost", port: 5432 } }, { id: DatabaseConnection, useFactory: async (config) => { const conn = new DatabaseConnection(config); await conn.connect(); return conn; }, inject: ["dbConfig"], }, ], exports: [DatabaseConnection], async dispose(container) { const conn = container.get(DatabaseConnection); await conn.disconnect(); }, }; const appModule: Module = { imports: [databaseModule], providers: [UserRepository], exports: [UserRepository], }; const container = await createContainer(appModule); const repo = container.get(UserRepository); ``` -------------------------------- ### Dynamic Service Registration at Runtime Source: https://github.com/denostack/monopole/blob/main/_autodocs/advanced-usage.md Dynamically create modules based on runtime conditions, such as configuration settings. This allows for flexible application setup where services like databases, caching, or message queues can be included or excluded as needed. ```typescript async function createDynamicModule(config: any): Promise { const providers: Provider[] = [Logger]; const exports: ServiceIdentifier[] = [Logger]; if (config.useDatabase) { providers.push(Database); exports.push(Database); } if (config.useCaching) { providers.push(CacheService); exports.push(CacheService); } if (config.useMessageQueue) { providers.push(MessageQueue); exports.push(MessageQueue); } return { providers, exports, }; } const config = await loadConfig(); const dynamicModule = await createDynamicModule(config); const appModule: Module = { imports: [dynamicModule], providers: [AppService], exports: [AppService], }; const container = await createContainer(appModule); ``` -------------------------------- ### Register Async Factory for Initialization Hooks Source: https://github.com/denostack/monopole/blob/main/_autodocs/provider-patterns.md Use async factories for services that need to perform asynchronous setup, such as connecting to databases or loading configuration files. The `dispose` function can be used for cleanup. ```typescript const module: Module = { providers: [ { id: Database, useFactory: async (config) => { const db = new Database(config); await db.connect(); console.log("Database connected"); return db; }, inject: ["dbConfig"], }, ], exports: [Database], async dispose(container) { const db = container.get(Database); await db.disconnect(); console.log("Database disconnected"); }, }; ``` -------------------------------- ### Async Initialization with Factory Provider Source: https://github.com/denostack/monopole/blob/main/_autodocs/00-START-HERE.md Demonstrates an asynchronous factory provider for database connection, including boot and dispose lifecycle methods. ```typescript const module: Module = { providers: [{ id: Database, useFactory: async (config) => { const db = new Database(config); await db.connect(); return db; }, inject: ["dbConfig"], }], async boot(container) { // Called after initialization }, async dispose(container) { // Called during cleanup }, }; ``` -------------------------------- ### Get Container Entries Source: https://github.com/denostack/monopole/blob/main/_autodocs/api-signatures.md Returns an iterator of all [id, instance] pairs in the container. ```typescript abstract entries(): IterableIterator<[ServiceIdentifier, unknown]> ``` -------------------------------- ### Monopole Service Definition Source: https://github.com/denostack/monopole/blob/main/_autodocs/00-START-HERE.md Example of defining a simple service class that can be managed by the Monopole container. ```typescript class Logger { log(message: string) { console.log(message); } } ``` -------------------------------- ### Monopole Property Injection Source: https://github.com/denostack/monopole/blob/main/_autodocs/00-START-HERE.md Example of using the `@inject` decorator to automatically inject dependencies into class properties. ```typescript class UserService { @inject(Logger) logger!: Logger; // Automatically populated } ```