@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.
- package/README.md +166 -0
- package/ai/genkit/multi-region.plugin.js +5 -3
- package/ai/genkit/tests/multi-region.test.d.ts +1 -0
- package/ai/genkit/tests/multi-region.test.js +5 -2
- package/ai/parser/parser.js +2 -2
- package/ai/prompts/build.js +1 -0
- package/ai/prompts/instructions-formatter.d.ts +15 -2
- package/ai/prompts/instructions-formatter.js +36 -31
- package/ai/prompts/prompt-builder.js +5 -5
- package/ai/prompts/steering.d.ts +3 -2
- package/ai/prompts/steering.js +3 -1
- package/ai/tests/instructions-formatter.test.js +1 -0
- package/api/README.md +403 -0
- package/api/client/client.js +7 -13
- package/api/client/tests/api-client.test.js +10 -10
- package/api/default-error-handlers.js +1 -1
- package/api/response.d.ts +2 -2
- package/api/response.js +22 -33
- package/api/server/api-controller.d.ts +1 -1
- package/api/server/api-controller.js +3 -3
- package/api/server/api-request-token.provider.d.ts +1 -0
- package/api/server/api-request-token.provider.js +1 -0
- package/api/server/middlewares/allowed-methods.middleware.js +2 -1
- package/api/server/middlewares/content-type.middleware.js +2 -1
- package/api/types.d.ts +3 -2
- package/application/README.md +240 -0
- package/application/application.d.ts +1 -1
- package/application/application.js +3 -3
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/README.md +267 -0
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/README.md +288 -0
- package/authentication/client/authentication.service.d.ts +12 -11
- package/authentication/client/authentication.service.js +21 -21
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/browser/README.md +401 -0
- package/cancellation/README.md +156 -0
- package/cancellation/tests/coverage.test.d.ts +1 -0
- package/cancellation/tests/coverage.test.js +49 -0
- package/cancellation/tests/leak.test.js +24 -29
- package/cancellation/tests/token.test.d.ts +1 -0
- package/cancellation/tests/token.test.js +136 -0
- package/cancellation/token.d.ts +53 -177
- package/cancellation/token.js +132 -208
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/context/README.md +174 -0
- package/cookie/README.md +161 -0
- package/css/README.md +157 -0
- package/data-structures/README.md +320 -0
- package/decorators/README.md +140 -0
- package/distributed-loop/README.md +231 -0
- package/distributed-loop/distributed-loop.js +1 -1
- package/document-management/README.md +403 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/server/services/document-management.service.js +9 -7
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-management-core.test.js +2 -7
- package/document-management/tests/document-management.api.test.js +6 -7
- package/document-management/tests/document-statistics.service.test.js +11 -12
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/document-management/tests/document.service.test.js +3 -3
- package/document-management/tests/enum-helpers.test.js +2 -3
- package/dom/README.md +213 -0
- package/enumerable/README.md +259 -0
- package/enumeration/README.md +121 -0
- package/errors/README.md +267 -0
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/file/README.md +191 -0
- package/formats/README.md +210 -0
- package/function/README.md +144 -0
- package/http/README.md +318 -0
- package/http/client/adapters/undici.adapter.js +1 -1
- package/http/client/http-client-request.d.ts +6 -5
- package/http/client/http-client-request.js +8 -9
- package/http/server/node/node-http-server.js +1 -2
- package/image-service/README.md +137 -0
- package/injector/README.md +491 -0
- package/intl/README.md +113 -0
- package/json-path/README.md +182 -0
- package/jsx/README.md +154 -0
- package/key-value-store/README.md +191 -0
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/README.md +249 -0
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/memory/README.md +144 -0
- package/message-bus/README.md +244 -0
- package/message-bus/message-bus-base.js +1 -1
- package/module/README.md +182 -0
- package/module/module.d.ts +1 -1
- package/module/module.js +77 -17
- package/module/modules/web-server.module.js +3 -4
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-flow.test.js +2 -2
- package/notification/tests/notification-type.service.test.js +24 -15
- package/object-storage/README.md +300 -0
- package/openid-connect/README.md +274 -0
- package/orm/README.md +423 -0
- package/orm/decorators.d.ts +5 -1
- package/orm/decorators.js +1 -1
- package/orm/server/drizzle/schema-converter.js +17 -30
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/index.d.ts +1 -6
- package/orm/server/index.js +1 -6
- package/orm/server/migration.d.ts +19 -0
- package/orm/server/migration.js +72 -0
- package/orm/server/repository.d.ts +1 -1
- package/orm/server/transaction.d.ts +5 -10
- package/orm/server/transaction.js +22 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-migration.test.d.ts +1 -0
- package/orm/tests/database-migration.test.js +82 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +9 -6
- package/password/README.md +164 -0
- package/pdf/README.md +246 -0
- package/polyfills.js +1 -0
- package/pool/README.md +198 -0
- package/process/README.md +237 -0
- package/promise/README.md +252 -0
- package/promise/cancelable-promise.js +1 -1
- package/random/README.md +193 -0
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/README.md +305 -0
- package/reflection/decorator-data.js +11 -12
- package/rpc/README.md +386 -0
- package/rxjs-utils/README.md +262 -0
- package/schema/README.md +342 -0
- package/serializer/README.md +342 -0
- package/signals/implementation/README.md +134 -0
- package/sse/README.md +278 -0
- package/task-queue/README.md +293 -0
- package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +5 -1
- package/task-queue/postgres/schemas.d.ts +9 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +4 -13
- package/task-queue/postgres/task-queue.js +462 -355
- package/task-queue/postgres/task.model.d.ts +12 -5
- package/task-queue/postgres/task.model.js +51 -25
- package/task-queue/task-context.d.ts +2 -2
- package/task-queue/task-context.js +8 -8
- package/task-queue/task-queue.d.ts +53 -19
- package/task-queue/task-queue.js +121 -55
- package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
- package/task-queue/tests/cascading-cancellations.test.js +38 -0
- package/task-queue/tests/complex.test.js +45 -229
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +407 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +144 -0
- package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
- package/task-queue/tests/dag-dependencies.test.js +41 -0
- package/task-queue/tests/dependencies.test.js +28 -26
- package/task-queue/tests/extensive-dependencies.test.js +64 -139
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +53 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +61 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
- package/task-queue/tests/queue.test.js +128 -8
- package/task-queue/tests/worker.test.js +39 -16
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/templates/README.md +287 -0
- package/test5.js +5 -5
- package/testing/README.md +157 -0
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +54 -29
- package/text/README.md +346 -0
- package/text/localization.service.js +2 -2
- package/threading/README.md +238 -0
- package/types/README.md +311 -0
- package/utils/README.md +322 -0
- package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
- package/utils/async-iterable-helpers/observable-iterable.js +4 -8
- package/utils/async-iterable-helpers/take-until.js +4 -4
- package/utils/backoff.js +89 -30
- package/utils/file-reader.js +1 -2
- package/utils/retry-with-backoff.js +1 -1
- package/utils/timer.d.ts +1 -1
- package/utils/timer.js +5 -7
- package/utils/timing.d.ts +1 -1
- package/utils/timing.js +2 -4
- package/utils/z-base32.d.ts +1 -0
- 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. |
|