@tstdl/base 0.93.139 → 0.93.141

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 (218) 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.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
@@ -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'` |
package/intl/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @tstdl/base/intl
2
+
3
+ The `intl` module provides utilities for handling internationalization tasks, specifically focusing on robust, locale-aware number parsing. It allows you to convert formatted number strings (e.g., "1.234,56" in German) back into standard JavaScript numbers by correctly identifying decimal and group separators based on the locale.
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
+ - [Loose Parsing](#loose-parsing)
12
+ - [Non-Latin Numerals](#non-latin-numerals)
13
+ - [Performance & Memoization](#performance--memoization)
14
+ - [📚 API](#-api)
15
+
16
+ ## ✨ Features
17
+
18
+ - **Locale-Aware Parsing**: Handles `,`, `.`, and spaces as separators depending on the locale.
19
+ - **Group Separator Support**: Automatically handles thousands separators (e.g., `1,000` vs `1.000`).
20
+ - **Loose Mode**: Strips non-numeric characters (like currency symbols or units) before parsing.
21
+ - **Numeral System Support**: Automatically maps non-Latin numerals (e.g., Arabic-Indic `١٢٣`) to standard digits.
22
+ - **Memoization**: Optimized performance through cached parser instances.
23
+
24
+ ## Core Concepts
25
+
26
+ Standard JavaScript functions like `parseFloat()` or `Number()` are designed for machine-readable formats (usually English-based, using `.` for decimals and no thousands separators). They often fail or produce incorrect results when dealing with user input in other locales (e.g., `parseFloat('1.234,56')` results in `1.234` instead of `1234.56`).
27
+
28
+ The `NumberParser` class uses the native `Intl.NumberFormat` API to inspect the formatting rules of a given locale. It normalizes the input string by removing group separators and converting decimal separators to `.` before using the standard `Number()` constructor.
29
+
30
+ ## 🚀 Basic Usage
31
+
32
+ The most common way to use this module is via the `parseNumber` helper function.
33
+
34
+ ```typescript
35
+ import { parseNumber } from '@tstdl/base/intl';
36
+
37
+ // US English: Comma for thousands, Dot for decimals
38
+ const usValue = parseNumber('en-US', '12,345.67');
39
+ console.log(usValue); // 12345.67
40
+
41
+ // German: Dot for thousands, Comma for decimals
42
+ const deValue = parseNumber('de-DE', '12.345,67');
43
+ console.log(deValue); // 12345.67
44
+
45
+ // French: Space for thousands, Comma for decimals
46
+ const frValue = parseNumber('fr-FR', '12 345,67');
47
+ console.log(frValue); // 12345.67
48
+ ```
49
+
50
+ ## 🔧 Advanced Topics
51
+
52
+ ### Loose Parsing
53
+
54
+ When dealing with user input or scraped data, strings often contain currency symbols, units, or other text. The `loose` parameter (default `false`) instructs the parser to strip out any characters that are not valid numerals or separators for the target locale before parsing.
55
+
56
+ ```typescript
57
+ import { parseNumber } from '@tstdl/base/intl';
58
+
59
+ // Standard parsing fails on non-numeric characters
60
+ const strict = parseNumber('en-US', '$1,234.56 USD');
61
+ console.log(strict); // NaN
62
+
63
+ // Loose parsing strips the currency symbol and text
64
+ const loose = parseNumber('en-US', '$1,234.56 USD', true);
65
+ console.log(loose); // 1234.56
66
+
67
+ // Works with locale-specific formatting
68
+ const looseDe = parseNumber('de-DE', '1.234,56 €', true);
69
+ console.log(looseDe); // 1234.56
70
+ ```
71
+
72
+ ### Non-Latin Numerals
73
+
74
+ The module automatically handles locales that use non-Latin numeral systems by mapping them back to standard digits.
75
+
76
+ ```typescript
77
+ import { parseNumber } from '@tstdl/base/intl';
78
+
79
+ // Arabic (Egypt): Uses Arabic-Indic numerals
80
+ const arValue = parseNumber('ar-EG', '١٢٬٣٤٥٫٦٧');
81
+ console.log(arValue); // 12345.67
82
+ ```
83
+
84
+ ### Performance & Memoization
85
+
86
+ Creating a `NumberParser` instance involves some overhead. To mitigate this, the module exports `getNumberParser`, which memoizes instances based on the locale string. The `parseNumber` helper uses this internally.
87
+
88
+ ```typescript
89
+ import { getNumberParser } from '@tstdl/base/intl';
90
+
91
+ // Uses the memoized instance (Recommended)
92
+ const parser = getNumberParser('en-GB');
93
+ const value = parser.parse('10,000.50');
94
+ ```
95
+
96
+ ## 📚 API
97
+
98
+ ### Functions
99
+
100
+ | Function | Description |
101
+ | :--- | :--- |
102
+ | `parseNumber(locale: string, value: string, loose?: boolean): number` | Parses a localized string into a number. |
103
+ | `getNumberParser(locale: string): NumberParser` | Returns a memoized `NumberParser` instance for the specified locale. |
104
+
105
+ ### Classes
106
+
107
+ #### `NumberParser`
108
+
109
+ | Method / Property | Type | Description |
110
+ | :--- | :--- | :--- |
111
+ | `constructor(locale: string)` | `void` | Creates a new parser for the given locale. |
112
+ | `locale` | `string` | The locale string this parser was initialized with. |
113
+ | `parse(value: string, loose?: boolean): number` | `number` | Parses the string. If `loose` is true, strips invalid characters first. |