@tstdl/base 0.93.138 → 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.
- 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.js +2 -2
- package/audit/README.md +267 -0
- 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/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- 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.d.ts +1 -0
- package/cancellation/tests/leak.test.js +35 -0
- 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 -201
- 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/services/document-management.service.js +9 -7
- 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.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/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/injector/injector.d.ts +1 -0
- package/injector/injector.js +17 -5
- package/injector/tests/leak.test.d.ts +1 -0
- package/injector/tests/leak.test.js +45 -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/lock/README.md +249 -0
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- 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 +1 -1
- 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/package.json +8 -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/reflection/README.md +305 -0
- 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 +300 -0
- package/task-queue/postgres/task-queue.d.ts +2 -1
- package/task-queue/postgres/task-queue.js +32 -2
- package/task-queue/task-context.js +1 -1
- package/task-queue/task-queue.d.ts +17 -0
- package/task-queue/task-queue.js +103 -44
- package/task-queue/tests/complex.test.js +4 -4
- package/task-queue/tests/dependencies.test.js +4 -2
- package/task-queue/tests/queue.test.js +111 -0
- package/task-queue/tests/worker.test.js +21 -13
- package/templates/README.md +287 -0
- package/testing/README.md +157 -0
- package/text/README.md +346 -0
- 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/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,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'` |
|
package/injector/injector.d.ts
CHANGED
package/injector/injector.js
CHANGED
|
@@ -38,6 +38,9 @@ export class Injector {
|
|
|
38
38
|
get parent() {
|
|
39
39
|
return this.#parent;
|
|
40
40
|
}
|
|
41
|
+
get children() {
|
|
42
|
+
return this.#children;
|
|
43
|
+
}
|
|
41
44
|
get accesses() {
|
|
42
45
|
return this.#accesses;
|
|
43
46
|
}
|
|
@@ -50,9 +53,12 @@ export class Injector {
|
|
|
50
53
|
this.ownerToken = ownerToken;
|
|
51
54
|
this.register(Injector, { useValue: this });
|
|
52
55
|
this.register(CancellationSignal, { useValue: this.#disposeToken.signal });
|
|
53
|
-
this.#disposableStack.defer(() =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
this.#disposableStack.defer(() => {
|
|
57
|
+
this.#registrations.clear();
|
|
58
|
+
this.#injectorScopedResolutions.clear();
|
|
59
|
+
this.#disposableStackRegistrations.clear();
|
|
60
|
+
this.#accesses.length = 0;
|
|
61
|
+
});
|
|
56
62
|
}
|
|
57
63
|
/**
|
|
58
64
|
* Add a dispose handler to this injector. The handler can be a disposable, async disposable, or a simple function. If the handler is a disposable or async disposable, it will be disposed when the injector is disposed.
|
|
@@ -120,6 +126,12 @@ export class Injector {
|
|
|
120
126
|
const child = new Injector(name, this, ownerToken);
|
|
121
127
|
this.#children.push(child);
|
|
122
128
|
this.#disposableStack.use(child);
|
|
129
|
+
child.addDisposeHandler(() => {
|
|
130
|
+
const index = this.#children.indexOf(child);
|
|
131
|
+
if (index != -1) {
|
|
132
|
+
this.#children.splice(index, 1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
123
135
|
return child;
|
|
124
136
|
}
|
|
125
137
|
/**
|
|
@@ -395,8 +407,8 @@ export class Injector {
|
|
|
395
407
|
context.recordAccess(chain.withMetadata({ isCached: true }));
|
|
396
408
|
return registration.resolutions.get(argumentIdentity);
|
|
397
409
|
}
|
|
398
|
-
// A new scope is only needed if
|
|
399
|
-
const needsNewScope =
|
|
410
|
+
// A new scope is only needed if the registration explicitly defines scoped providers.
|
|
411
|
+
const needsNewScope = (providers.length > 0);
|
|
400
412
|
const injector = needsNewScope ? this.fork(`${getTokenName(token)}Injector`, token) : this;
|
|
401
413
|
for (const nestedProvider of providers) {
|
|
402
414
|
injector.registerSingleton(nestedProvider.provide, nestedProvider, { multi: nestedProvider.multi });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|