@tstdl/base 0.93.139 → 0.93.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.js +2 -2
  28. package/audit/README.md +267 -0
  29. package/authentication/README.md +288 -0
  30. package/authentication/client/authentication.service.d.ts +12 -11
  31. package/authentication/client/authentication.service.js +21 -21
  32. package/authentication/client/http-client.middleware.js +2 -2
  33. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  34. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  35. package/browser/README.md +401 -0
  36. package/cancellation/README.md +156 -0
  37. package/cancellation/tests/coverage.test.d.ts +1 -0
  38. package/cancellation/tests/coverage.test.js +49 -0
  39. package/cancellation/tests/leak.test.js +24 -29
  40. package/cancellation/tests/token.test.d.ts +1 -0
  41. package/cancellation/tests/token.test.js +136 -0
  42. package/cancellation/token.d.ts +53 -177
  43. package/cancellation/token.js +132 -208
  44. package/context/README.md +174 -0
  45. package/cookie/README.md +161 -0
  46. package/css/README.md +157 -0
  47. package/data-structures/README.md +320 -0
  48. package/decorators/README.md +140 -0
  49. package/distributed-loop/README.md +231 -0
  50. package/distributed-loop/distributed-loop.js +1 -1
  51. package/document-management/README.md +403 -0
  52. package/document-management/server/services/document-management.service.js +9 -7
  53. package/document-management/tests/document-management-core.test.js +2 -7
  54. package/document-management/tests/document-management.api.test.js +6 -7
  55. package/document-management/tests/document-statistics.service.test.js +11 -12
  56. package/document-management/tests/document.service.test.js +3 -3
  57. package/document-management/tests/enum-helpers.test.js +2 -3
  58. package/dom/README.md +213 -0
  59. package/enumerable/README.md +259 -0
  60. package/enumeration/README.md +121 -0
  61. package/errors/README.md +267 -0
  62. package/file/README.md +191 -0
  63. package/formats/README.md +210 -0
  64. package/function/README.md +144 -0
  65. package/http/README.md +318 -0
  66. package/http/client/adapters/undici.adapter.js +1 -1
  67. package/http/client/http-client-request.d.ts +6 -5
  68. package/http/client/http-client-request.js +8 -9
  69. package/http/server/node/node-http-server.js +1 -2
  70. package/image-service/README.md +137 -0
  71. package/injector/README.md +491 -0
  72. package/intl/README.md +113 -0
  73. package/json-path/README.md +182 -0
  74. package/jsx/README.md +154 -0
  75. package/key-value-store/README.md +191 -0
  76. package/lock/README.md +249 -0
  77. package/lock/web/web-lock.js +119 -47
  78. package/logger/README.md +287 -0
  79. package/mail/README.md +256 -0
  80. package/memory/README.md +144 -0
  81. package/message-bus/README.md +244 -0
  82. package/message-bus/message-bus-base.js +1 -1
  83. package/module/README.md +182 -0
  84. package/module/module.d.ts +1 -1
  85. package/module/module.js +77 -17
  86. package/module/modules/web-server.module.js +1 -1
  87. package/notification/tests/notification-type.service.test.js +24 -15
  88. package/object-storage/README.md +300 -0
  89. package/openid-connect/README.md +274 -0
  90. package/orm/README.md +423 -0
  91. package/package.json +8 -6
  92. package/password/README.md +164 -0
  93. package/pdf/README.md +246 -0
  94. package/polyfills.js +1 -0
  95. package/pool/README.md +198 -0
  96. package/process/README.md +237 -0
  97. package/promise/README.md +252 -0
  98. package/promise/cancelable-promise.js +1 -1
  99. package/random/README.md +193 -0
  100. package/reflection/README.md +305 -0
  101. package/rpc/README.md +386 -0
  102. package/rxjs-utils/README.md +262 -0
  103. package/schema/README.md +342 -0
  104. package/serializer/README.md +342 -0
  105. package/signals/implementation/README.md +134 -0
  106. package/sse/README.md +278 -0
  107. package/task-queue/README.md +300 -0
  108. package/task-queue/postgres/task-queue.d.ts +2 -1
  109. package/task-queue/postgres/task-queue.js +32 -2
  110. package/task-queue/task-context.js +1 -1
  111. package/task-queue/task-queue.d.ts +17 -0
  112. package/task-queue/task-queue.js +103 -45
  113. package/task-queue/tests/complex.test.js +4 -4
  114. package/task-queue/tests/dependencies.test.js +4 -2
  115. package/task-queue/tests/queue.test.js +111 -0
  116. package/task-queue/tests/worker.test.js +21 -13
  117. package/templates/README.md +287 -0
  118. package/testing/README.md +157 -0
  119. package/text/README.md +346 -0
  120. package/threading/README.md +238 -0
  121. package/types/README.md +311 -0
  122. package/utils/README.md +322 -0
  123. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  124. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  125. package/utils/async-iterable-helpers/take-until.js +4 -4
  126. package/utils/backoff.js +89 -30
  127. package/utils/retry-with-backoff.js +1 -1
  128. package/utils/timer.d.ts +1 -1
  129. package/utils/timer.js +5 -7
  130. package/utils/timing.d.ts +1 -1
  131. package/utils/timing.js +2 -4
  132. package/utils/z-base32.d.ts +1 -0
  133. package/utils/z-base32.js +1 -0
@@ -0,0 +1,137 @@
1
+ # Image Service
2
+
3
+ A robust module for generating signed, manipulated image URLs, providing an abstract interface for image processing services with a concrete implementation for [imgproxy](https://imgproxy.net/).
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [🔧 Advanced Topics](#-advanced-topics)
11
+ - [Image Options](#image-options)
12
+ - [Manual Instantiation](#manual-instantiation)
13
+ - [📚 API](#-api)
14
+
15
+ ## ✨ Features
16
+
17
+ - **Abstract Interface**: `ImageService` base class allows for dependency injection and easy swapping of image providers.
18
+ - **Imgproxy Support**: Full implementation for generating signed imgproxy URLs.
19
+ - **Secure Signing**: Automatically handles HMAC-SHA256 signature generation using your key and salt.
20
+ - **Rich Manipulations**: Supports resizing, cropping (gravity/origin), formatting, quality adjustment, and cache busting.
21
+ - **Type-Safe Options**: Fully typed configuration and options using TypeScript enums and classes.
22
+
23
+ ## Core Concepts
24
+
25
+ The module revolves around the `ImageService` abstract class. By depending on this abstraction in your application, you decouple your code from the specific image processing backend.
26
+
27
+ The primary implementation provided is `ImgproxyImageService`. It generates URLs that point to an imgproxy server. These URLs include:
28
+
29
+ 1. **Processing Options**: Instructions for resizing, formatting, etc.
30
+ 2. **Encoded Source URL**: The original image URL, Base64 encoded.
31
+ 3. **Signature**: A cryptographic signature ensuring the URL parameters haven't been tampered with, preventing denial-of-service attacks via image processing.
32
+
33
+ ## 🚀 Basic Usage
34
+
35
+ ### 1. Configuration
36
+
37
+ First, configure the service during your application bootstrap. This registers `ImgproxyImageService` as the implementation for `ImageService` in the dependency injection container.
38
+
39
+ ```ts
40
+ import { configureImgproxyImageService } from '@tstdl/base/image-service';
41
+
42
+ // In your bootstrap or configuration file
43
+ configureImgproxyImageService({
44
+ endpoint: 'https://images.yourdomain.com',
45
+ key: 'your-hex-encoded-key',
46
+ salt: 'your-hex-encoded-salt',
47
+ signatureSize: 32,
48
+ });
49
+ ```
50
+
51
+ ### 2. Generating URLs
52
+
53
+ Inject the `ImageService` and use `getUrl` to generate signed URLs for your images.
54
+
55
+ ```ts
56
+ import { ImageService, ImageResizeMode } from '@tstdl/base/image-service';
57
+ import { inject } from '@tstdl/base/injector';
58
+
59
+ class UserProfileController {
60
+ // Inject the abstract service
61
+ readonly #imageService = inject(ImageService);
62
+
63
+ async getAvatarUrl(originalUrl: string): Promise<string> {
64
+ // Generate a signed URL
65
+ const signedUrl = await this.#imageService.getUrl(originalUrl, {
66
+ width: 150,
67
+ height: 150,
68
+ resizeMode: ImageResizeMode.Fill,
69
+ });
70
+
71
+ return signedUrl;
72
+ // Output example:
73
+ // https://images.yourdomain.com/SIGNATURE/rs:fill:150:150/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## 🔧 Advanced Topics
79
+
80
+ ### Image Options
81
+
82
+ The `ImageOptions` class provides granular control over the output image.
83
+
84
+ ```ts
85
+ import { ImageService, ImageResizeMode, ImageOrigin, ImageFormat } from '@tstdl/base/image-service';
86
+ import { inject } from '@tstdl/base/injector';
87
+
88
+ const imageService = inject(ImageService);
89
+
90
+ const url = await imageService.getUrl('https://example.com/banner.png', {
91
+ // Resize strategy
92
+ resizeMode: ImageResizeMode.Fit, // 'fit' or 'fill'
93
+ width: 800,
94
+ height: 400,
95
+
96
+ // Gravity / Origin (where to focus when cropping)
97
+ origin: ImageOrigin.Smart, // Uses smart detection if supported, or center
98
+
99
+ // Output format and quality
100
+ format: ImageFormat.Webp,
101
+ quality: 80,
102
+
103
+ // Cache busting string (appended to processing options)
104
+ cacheBuster: 'v1.2.3',
105
+ });
106
+ ```
107
+
108
+ ### Manual Instantiation
109
+
110
+ If you need to instantiate `ImgproxyImageService` directly (outside of the DI container) or manage multiple instances with different configurations:
111
+
112
+ ```ts
113
+ import { ImgproxyImageService } from '@tstdl/base/image-service';
114
+
115
+ const imgproxy = new ImgproxyImageService(
116
+ 'https://img.example.com', // endpoint
117
+ 'aabbcc...', // key (hex)
118
+ '112233...', // salt (hex)
119
+ 32, // signature size
120
+ );
121
+
122
+ const url = await imgproxy.getUrl('http://source.com/img.jpg', { width: 100 });
123
+ ```
124
+
125
+ ## 📚 API
126
+
127
+ | Export | Type | Description |
128
+ | :------------------------------ | :--------------- | :----------------------------------------------------------------------------------------- |
129
+ | `ImageService` | `abstract class` | The base contract for image services. Defines `getUrl`. |
130
+ | `ImgproxyImageService` | `class` | Concrete implementation for imgproxy. |
131
+ | `configureImgproxyImageService` | `function` | Helper to register the imgproxy service and config in the injector. |
132
+ | `ImageOptions` | `class` | Configuration object for image manipulation (width, height, etc.). |
133
+ | `ImageResizeMode` | `enum` | Resize strategies: `Fit`, `Fill`. |
134
+ | `ImageFormat` | `enum` | Output formats: `Png`, `Jpg`, `Webp`, `Avif`, etc. |
135
+ | `ImageOrigin` | `enum` | Gravity/Origin for cropping: `Center`, `Smart`, `Top`, `TopLeft`, etc. |
136
+ | `IMGPROXY_IMAGE_SERVICE_CONFIG` | `InjectionToken` | Token used to inject the configuration object. |
137
+ | `ImgproxyImageServiceConfig` | `type` | Type definition for the configuration object (`endpoint`, `key`, `salt`, `signatureSize`). |
@@ -0,0 +1,491 @@
1
+ # @tstdl/base/injector
2
+
3
+ A powerful, flexible, and type-safe Dependency Injection (DI) framework for TypeScript. It combines modern ergonomics like property injection with advanced capabilities such as circular dependency resolution, asynchronous initialization, and hierarchical scoping.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [🔧 Advanced Topics](#-advanced-topics)
11
+ - [Constructor Injection](#constructor-injection)
12
+ - [Injection Tokens](#injection-tokens)
13
+ - [Factory Providers](#factory-providers)
14
+ - [Lifecycle Scopes](#lifecycle-scopes)
15
+ - [Asynchronous Initialization](#asynchronous-initialization)
16
+ - [Resource Management & Cleanup](#resource-management--cleanup)
17
+ - [Optional Dependencies](#optional-dependencies)
18
+ - [Circular Dependencies](#circular-dependencies)
19
+ - [Multi-Providers](#multi-providers)
20
+ - [Resolution Arguments](#resolution-arguments)
21
+ - [Argument Forwarding](#argument-forwarding)
22
+ - [Aliases](#aliases)
23
+ - [Decorator Inheritance](#decorator-inheritance)
24
+ - [Hierarchical Injectors](#hierarchical-injectors)
25
+ - [📚 API](#-api)
26
+
27
+ ## ✨ Features
28
+
29
+ - **Type-Safe & Decorator-Driven:** Use decorators like `@Injectable` and `@Singleton` to configure dependencies.
30
+ - **Ergonomic Property Injection:** Clean syntax using `inject()` in field initializers, removing constructor boilerplate.
31
+ - **Flexible Constructor Injection:** Traditional constructor injection is fully supported.
32
+ - **Comprehensive Lifecycles:** `singleton`, `injector`, `resolution`, and `transient` scopes.
33
+ - **Hierarchical Injectors:** Create isolated or inheriting scopes using `injector.fork()`.
34
+ - **Injection Tokens:** Support for interfaces and configuration objects via `injectionToken()`.
35
+ - **Circular Dependency Handling:** Automatic resolution of cycles using `forwardRef`.
36
+ - **Asynchronous Initialization:** Built-in support for async setup via `afterResolve` hooks.
37
+ - **Automatic Cleanup:** Integrated with `AsyncDisposable` and `addDisposeHandler` for resource management.
38
+ - **Decorator Inheritance:** Inherit injection options from base classes automatically.
39
+ - **Context-Aware:** `runInInjectionContext` allows using DI features outside of class instantiation.
40
+
41
+ ## Core Concepts
42
+
43
+ ### Injector
44
+
45
+ The container that holds provider registrations and manages the creation of instances. Injectors can be nested to create hierarchies.
46
+
47
+ ### Token
48
+
49
+ A unique identifier for a dependency. This is usually a class constructor, but can be a custom `InjectionToken` for non-class values (like configuration objects or interfaces).
50
+
51
+ ### Provider
52
+
53
+ Defines _how_ a token is resolved. Supported strategies include:
54
+
55
+ - **`useClass`**: Creates an instance of a class.
56
+ - **`useValue`**: Returns a specific value.
57
+ - **`useFactory`**: Executes a function to produce the value.
58
+ - **`useToken`**: Aliases one token to another.
59
+
60
+ ### Injection Context
61
+
62
+ A temporary state active during the instantiation of a class by the Injector. The `inject()` function works only within this context (e.g., field initializers or constructors).
63
+
64
+ ## 🚀 Basic Usage
65
+
66
+ The most common pattern uses `@Singleton()` or `@Injectable()` decorators and property injection via `inject()`.
67
+
68
+ ```typescript
69
+ import { Injector, Singleton, inject } from '@tstdl/base/injector';
70
+
71
+ // 1. Define a dependency
72
+ @Singleton()
73
+ export class LoggerService {
74
+ log(message: string): void {
75
+ console.log(`[LOG]: ${message}`);
76
+ }
77
+ }
78
+
79
+ // 2. Define a consumer
80
+ @Singleton()
81
+ export class UserService {
82
+ // Property injection: cleaner than constructor parameters
83
+ private readonly logger = inject(LoggerService);
84
+
85
+ getUser(id: number) {
86
+ this.logger.log(`Fetching user ${id}`);
87
+ return { id, name: 'Alice' };
88
+ }
89
+ }
90
+
91
+ // 3. Create the injector and resolve
92
+ const injector = new Injector('Root');
93
+ const userService = injector.resolve(UserService);
94
+
95
+ userService.getUser(42);
96
+ // Output: [LOG]: Fetching user 42
97
+ ```
98
+
99
+ ### Manual Registration
100
+
101
+ You can also register providers manually on an injector or globally.
102
+
103
+ ```typescript
104
+ // Global registration (affects all new root injectors)
105
+ Injector.registerSingleton(LoggerService, { useClass: ConsoleLogger });
106
+
107
+ // Local registration (only for this injector and its children)
108
+ const injector = new Injector('Root');
109
+ injector.register(LoggerService, { useValue: myMockLogger });
110
+ ```
111
+
112
+ ## 🔧 Advanced Topics
113
+
114
+ ### Constructor Injection
115
+
116
+ Traditional constructor injection is fully supported. If you use TypeScript with `emitDecoratorMetadata` enabled, types are inferred automatically. Otherwise, or for interfaces/tokens, use the `@Inject()` decorator.
117
+
118
+ ```typescript
119
+ import { Injectable, Inject } from '@tstdl/base/injector';
120
+ import { LoggerService } from './logger.service.js';
121
+
122
+ @Injectable()
123
+ export class ProductService {
124
+ constructor(
125
+ // Type inferred automatically if metadata is emitted
126
+ private readonly logger: LoggerService,
127
+ ) {
128
+ this.logger.log('ProductService initialized');
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### Injection Tokens
134
+
135
+ Use `injectionToken` for dependencies that aren't classes, such as configuration objects or primitives.
136
+
137
+ ```typescript
138
+ import { Injector, inject, injectionToken, Injectable } from '@tstdl/base/injector';
139
+
140
+ interface AppConfig {
141
+ apiUrl: string;
142
+ }
143
+
144
+ // Create a typed token
145
+ export const APP_CONFIG = injectionToken<AppConfig>('APP_CONFIG');
146
+
147
+ @Injectable()
148
+ class ApiClient {
149
+ private readonly config = inject(APP_CONFIG);
150
+
151
+ get url() {
152
+ return this.config.apiUrl;
153
+ }
154
+ }
155
+
156
+ const injector = new Injector('Root');
157
+
158
+ // Register the value for the token
159
+ injector.register(APP_CONFIG, {
160
+ useValue: { apiUrl: 'https://api.example.com' },
161
+ });
162
+
163
+ const client = injector.resolve(ApiClient);
164
+ console.log(client.url); // https://api.example.com
165
+ ```
166
+
167
+ ### Factory Providers
168
+
169
+ Factories allow complex creation logic, capable of using `inject()` internally.
170
+
171
+ ```typescript
172
+ import { Injector, inject, injectionToken } from '@tstdl/base/injector';
173
+
174
+ class Database {
175
+ constructor(public connectionString: string) {}
176
+ }
177
+
178
+ const DB_CONN_STRING = injectionToken<string>('DB_CONN_STRING');
179
+ const DATABASE = injectionToken<Database>('DATABASE');
180
+
181
+ const injector = new Injector('Root');
182
+
183
+ injector.register(DB_CONN_STRING, { useValue: 'postgres://localhost:5432' });
184
+
185
+ injector.register(DATABASE, {
186
+ useFactory: () => {
187
+ // Factories run in an injection context
188
+ const connString = inject(DB_CONN_STRING);
189
+ return new Database(connString);
190
+ },
191
+ });
192
+ ```
193
+
194
+ ### Lifecycle Scopes
195
+
196
+ Control instance sharing using the `lifecycle` option in `@Injectable` or `@Scoped`.
197
+
198
+ ```typescript
199
+ import { Singleton, Scoped, Injectable } from '@tstdl/base/injector';
200
+
201
+ // One instance per root injector (application-wide singleton)
202
+ @Singleton()
203
+ class AuthService {}
204
+
205
+ // One instance per injector (useful for hierarchical injectors)
206
+ @Scoped('injector')
207
+ class ModuleService {}
208
+
209
+ // One instance per .resolve() call tree
210
+ @Scoped('resolution')
211
+ class RequestContext {}
212
+
213
+ // New instance every time it is injected (default for @Injectable)
214
+ @Injectable() // or @Scoped('transient')
215
+ class TransientService {}
216
+ ```
217
+
218
+ ### Asynchronous Initialization
219
+
220
+ Implement the `Resolvable` interface and the `[afterResolve]` symbol to perform asynchronous setup. You must use `resolveAsync` or `injectAsync` to ensure the promise is awaited.
221
+
222
+ ```typescript
223
+ import { Singleton, afterResolve, Resolvable, Injector } from '@tstdl/base/injector';
224
+
225
+ @Singleton()
226
+ export class DatabaseService implements Resolvable {
227
+ isConnected = false;
228
+
229
+ // Called automatically after instantiation
230
+ async [afterResolve](): Promise<void> {
231
+ await new Promise((resolve) => setTimeout(resolve, 100));
232
+ this.isConnected = true;
233
+ }
234
+ }
235
+
236
+ const injector = new Injector('Root');
237
+
238
+ // Use resolveAsync to await the [afterResolve] hook
239
+ const db = await injector.resolveAsync(DatabaseService);
240
+ console.log(db.isConnected); // true
241
+ ```
242
+
243
+ ### Resource Management & Cleanup
244
+
245
+ The `Injector` implements `AsyncDisposable`. When an injector is disposed, all instances created within it that implement `Disposable` or `AsyncDisposable` are also disposed. You can also register custom cleanup logic using `addDisposeHandler` in the `afterResolve` hook or within a factory.
246
+
247
+ ```typescript
248
+ import { Singleton, afterResolve, Resolvable, AfterResolveContext } from '@tstdl/base/injector';
249
+
250
+ @Singleton()
251
+ export class DatabaseConnection implements Resolvable, AsyncDisposable {
252
+ async [afterResolve](_arg: any, { addDisposeHandler }: AfterResolveContext): Promise<void> {
253
+ await this.connect();
254
+
255
+ // Register a manual cleanup handler
256
+ addDisposeHandler(async () => await this.disconnect());
257
+ }
258
+
259
+ async [Symbol.asyncDispose](): Promise<void> {
260
+ // This will also be called when the injector is disposed
261
+ }
262
+ }
263
+
264
+ const injector = new Injector('Root');
265
+ await injector.resolveAsync(DatabaseConnection);
266
+ await injector.dispose(); // Triggers all cleanup handlers
267
+ ```
268
+
269
+ ### Optional Dependencies
270
+
271
+ Use the `@Optional()` decorator or the `optional: true` option in `inject()` to allow dependencies to be `undefined` if no provider is registered.
272
+
273
+ ```typescript
274
+ import { Injectable, Optional, inject } from '@tstdl/base/injector';
275
+
276
+ @Injectable()
277
+ class NotificationService {
278
+ // Using decorator
279
+ @Optional()
280
+ private readonly logger?: LoggerService;
281
+
282
+ // Using inject()
283
+ private readonly analytics = inject(AnalyticsService, undefined, { optional: true });
284
+ }
285
+ ```
286
+
287
+ ### Circular Dependencies
288
+
289
+ The injector can handle circular dependencies (A needs B, B needs A) using `forwardRef`.
290
+
291
+ ```typescript
292
+ import { Injectable, inject } from '@tstdl/base/injector';
293
+ // Assume ServiceB is imported here
294
+
295
+ @Injectable()
296
+ class ServiceA {
297
+ // Use forwardRef option in inject()
298
+ private readonly b = inject(ServiceB, undefined, { forwardRef: true });
299
+ }
300
+
301
+ @Injectable()
302
+ class ServiceB {
303
+ private readonly a = inject(ServiceA);
304
+ }
305
+ ```
306
+
307
+ ### Multi-Providers
308
+
309
+ Register multiple providers for the same token and inject them as an array using `injectAll`.
310
+
311
+ ```typescript
312
+ import { Injector, injectAll, injectionToken, Injectable } from '@tstdl/base/injector';
313
+
314
+ interface Plugin {
315
+ name: string;
316
+ }
317
+
318
+ const PLUGIN = injectionToken<Plugin>('PLUGIN');
319
+
320
+ @Injectable()
321
+ class PluginA implements Plugin {
322
+ name = 'A';
323
+ }
324
+
325
+ @Injectable()
326
+ class PluginB implements Plugin {
327
+ name = 'B';
328
+ }
329
+
330
+ const injector = new Injector('Root');
331
+
332
+ // Register multiple implementations with multi: true
333
+ injector.register(PLUGIN, { useClass: PluginA }, { multi: true });
334
+ injector.register(PLUGIN, { useClass: PluginB }, { multi: true });
335
+
336
+ @Injectable()
337
+ class PluginManager {
338
+ // Injects an array of all registered plugins
339
+ private readonly plugins = injectAll(PLUGIN);
340
+
341
+ list() {
342
+ return this.plugins.map((p) => p.name);
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### Resolution Arguments
348
+
349
+ You can pass runtime arguments to `resolve()`, which can be accessed anywhere in the resolution tree via `injectArgument()`.
350
+
351
+ ```typescript
352
+ import { Injector, Injectable, injectArgument } from '@tstdl/base/injector';
353
+
354
+ type UserContext = { userId: string };
355
+
356
+ @Injectable()
357
+ class UserProfile {
358
+ // Access the argument passed to resolve()
359
+ private readonly context = injectArgument<UserContext>();
360
+
361
+ get id() {
362
+ return this.context.userId;
363
+ }
364
+ }
365
+
366
+ const injector = new Injector('Root');
367
+ const profile = injector.resolve(UserProfile, { userId: 'user-123' });
368
+
369
+ console.log(profile.id); // user-123
370
+ ```
371
+
372
+ ### Argument Forwarding
373
+
374
+ You can forward or map resolution arguments down the injection tree using `@ForwardArg()` or `@InjectArg()`.
375
+
376
+ ```typescript
377
+ import { Injectable, ForwardArg, InjectArg } from '@tstdl/base/injector';
378
+
379
+ @Injectable()
380
+ class ServiceB {
381
+ // Injects the entire argument passed to the root .resolve() call
382
+ @InjectArg()
383
+ private readonly config: any;
384
+ }
385
+
386
+ @Injectable()
387
+ class ServiceA {
388
+ // Forwards a specific property of the argument to ServiceB
389
+ @ForwardArg((arg: any) => arg.subConfig)
390
+ private readonly b = inject(ServiceB);
391
+ }
392
+ ```
393
+
394
+ ### Aliases
395
+
396
+ Classes can be registered under multiple tokens using the `alias` option. This is useful for providing multiple implementations of an interface or handling circular dependencies.
397
+
398
+ ```typescript
399
+ import { Singleton } from '@tstdl/base/injector';
400
+
401
+ export const LOGGER_TOKEN = injectionToken<Logger>('LOGGER');
402
+
403
+ @Singleton({ alias: LOGGER_TOKEN })
404
+ class ConsoleLogger implements Logger {
405
+ log(msg: string) { console.log(msg); }
406
+ }
407
+ ```
408
+
409
+ ### Decorator Inheritance
410
+
411
+ By default, `@Injectable` and `@Singleton` options (like `lifecycle`, `alias`, etc.) are inherited from parent classes. You can control this behavior using `inheritOptions`.
412
+
413
+ ```typescript
414
+ @Singleton({ alias: BaseToken })
415
+ class BaseService {}
416
+
417
+ // Inherits 'singleton' lifecycle and 'alias' from BaseService
418
+ @Injectable()
419
+ class ExtendedService extends BaseService {}
420
+
421
+ // Disables inheritance
422
+ @Injectable({ inheritOptions: false })
423
+ class IsolatedService extends BaseService {}
424
+ ```
425
+
426
+ ### Hierarchical Injectors
427
+
428
+ Use `fork()` to create child injectors. Child injectors inherit providers from their parent but can override them or register new ones.
429
+
430
+ ```typescript
431
+ const parent = new Injector('Parent');
432
+ parent.register(LoggerService, { useClass: ConsoleLogger });
433
+
434
+ const child = parent.fork('Child');
435
+ // Child can resolve LoggerService from parent
436
+ const logger = child.resolve(LoggerService);
437
+ ```
438
+
439
+ ## 📚 API
440
+
441
+ ### Classes
442
+
443
+ | Class | Description |
444
+ | :------------------ | :------------------------------------------------------------------------------------ |
445
+ | `Injector` | The main dependency injection container. implements `AsyncDisposable`. |
446
+ | `ResolveChain` | Represents the path taken during dependency resolution (useful for debugging errors). |
447
+ | `ResolveError` | Error thrown when resolution fails (e.g., missing provider, circular dependency). |
448
+ | `RegistrationError` | Error thrown during provider registration (e.g., invalid configuration). |
449
+
450
+ ### Decorators
451
+
452
+ | Decorator | Description |
453
+ | :---------------------------------- | :-------------------------------------------------------------------- |
454
+ | `@Injectable(options?)` | Marks a class as available for injection. |
455
+ | `@Singleton(options?)` | Alias for `@Injectable({ lifecycle: 'singleton' })`. |
456
+ | `@Scoped(lifecycle, options?)` | Alias for `@Injectable` with a specific lifecycle. |
457
+ | `@Inject(token?, arg?, mapper?)` | Manually specifies the token for a constructor parameter or property. |
458
+ | `@InjectAll(token?, arg?, mapper?)` | Injects all providers for a token as an array. |
459
+ | `@Optional()` | Marks a dependency as optional (injects `undefined` if not found). |
460
+ | `@ForwardRef(tokenFn)` | Handles circular dependencies in constructor injection. |
461
+ | `@InjectArg(mapper?)` | Injects the resolution argument into a constructor parameter. |
462
+ | `@ForwardArg(mapper?)` | Forwards/maps the resolution argument to a dependency. |
463
+ | `@ReplaceClass(constructor)` | Replaces a class definition with another (useful for typing). |
464
+ | `@ResolveArg(value)` | Provides a static resolution argument for a dependency. |
465
+ | `@ResolveArgProvider(provider)` | Provides a dynamic resolution argument for a dependency. |
466
+
467
+ ### Functions
468
+
469
+ | Function | Description |
470
+ | :-------------------------------------- | :-------------------------------------------------------------------- |
471
+ | `inject(token, arg?, options?)` | Injects a dependency. Must be used within an injection context. |
472
+ | `injectAsync(token, arg?, options?)` | Asynchronously injects a dependency (awaits `afterResolve`). |
473
+ | `injectAll(token, arg?, options?)` | Injects all providers for a token as an array. |
474
+ | `injectAllAsync(token, arg?, options?)` | Asynchronously injects all providers for a token. |
475
+ | `injectMany(...tokens)` | Injects multiple tokens at once, returning a tuple. |
476
+ | `injectManyAsync(...tokens)` | Asynchronously injects multiple tokens at once. |
477
+ | `injectArgument()` | Retrieves the argument passed to `injector.resolve()`. |
478
+ | `injectionToken(description)` | Creates a unique token for non-class dependencies. |
479
+ | `provide(token, provider)` | Helper to create a provider object with a `provide` property. |
480
+ | `runInInjectionContext(injector, fn)` | Executes a function within an injection context, enabling `inject()`. |
481
+ | `getCurrentInjector()` | Returns the currently active injector instance. |
482
+
483
+ ### Interfaces & Types
484
+
485
+ | Type | Description |
486
+ | :------------------ | :----------------------------------------------------------------------------------- |
487
+ | `Provider<T>` | Union type for `ClassProvider`, `ValueProvider`, `FactoryProvider`, `TokenProvider`. |
488
+ | `InjectionToken<T>` | Type for a class constructor or a custom token. |
489
+ | `Resolvable<A>` | Interface for classes that need `afterResolve` hooks or typed resolution arguments. |
490
+ | `AfterResolve<A>` | Interface defining the `[afterResolve]` method signature. |
491
+ | `Lifecycle` | `'transient' \| 'singleton' \| 'injector' \| 'resolution'` |