@tstdl/base 0.93.139 → 0.93.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.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/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/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 -45
- 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,244 @@
|
|
|
1
|
+
# Message Bus
|
|
2
|
+
|
|
3
|
+
A flexible, RxJS-based message bus module for decoupled in-process and cross-context communication, designed for seamless integration with dependency injection.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Implementations](#implementations)
|
|
10
|
+
- [Message Observables](#message-observables)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [1. Configuration](#1-configuration)
|
|
13
|
+
- [2. Injection & Usage](#2-injection--usage)
|
|
14
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
15
|
+
- [Cross-Context Communication (BroadcastChannel)](#cross-context-communication-broadcastchannel)
|
|
16
|
+
- [Fire and Forget](#fire-and-forget)
|
|
17
|
+
- [Resource Management](#resource-management)
|
|
18
|
+
- [📚 API](#-api)
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- **Decoupled Communication**: Enables components to communicate via named channels without direct references.
|
|
23
|
+
- **Reactive Architecture**: Built on **RxJS**, providing powerful operators for filtering and transforming message streams.
|
|
24
|
+
- **Dual Modes & Cross-Platform**:
|
|
25
|
+
- **Local**: High-performance in-memory communication using RxJS Subjects.
|
|
26
|
+
- **BroadcastChannel**: Cross-context communication (tabs, windows, workers) using the standard Web API.
|
|
27
|
+
- **DI Integration**: Implements `Resolvable`, allowing direct injection with channel names as arguments.
|
|
28
|
+
- **Type Safety**: Fully supports generic types for message payloads.
|
|
29
|
+
- **Smart Filtering**: Distinguishes between messages from _other_ instances and _all_ messages (including self).
|
|
30
|
+
- **Memory Safe**: Implements `AsyncDisposable` for proper cleanup of subscriptions and channels. Uses `WeakRefMap` internally to prevent memory leaks on unused channels.
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
### Implementations
|
|
35
|
+
|
|
36
|
+
The module provides two strategies, selectable via configuration at application startup:
|
|
37
|
+
|
|
38
|
+
1. **`LocalMessageBus`**:
|
|
39
|
+
- **Scope**: Single JavaScript environment (e.g., one browser tab, one Node.js process).
|
|
40
|
+
- **Mechanism**: Shared RxJS `Subject`.
|
|
41
|
+
- **Use Case**: Component-to-component communication, global event bus within an app.
|
|
42
|
+
|
|
43
|
+
2. **`BroadcastChannelMessageBus`**:
|
|
44
|
+
- **Scope**: Same-origin browsing contexts (e.g., multiple tabs, iframes, workers).
|
|
45
|
+
- **Mechanism**: Wraps the `BroadcastChannel` API.
|
|
46
|
+
- **Use Case**: Synchronizing state across tabs (e.g., logout in one tab affects all others).
|
|
47
|
+
|
|
48
|
+
### Message Observables
|
|
49
|
+
|
|
50
|
+
Every `MessageBus` instance exposes two streams to handle different scenarios:
|
|
51
|
+
|
|
52
|
+
- **`messages$`**: Emits messages published by **other** instances on the same channel. It filters out messages sent by _this_ specific instance. Use this to react to external events.
|
|
53
|
+
- **`allMessages$`**: Emits **every** message on the channel, including those published by _this_ instance. Use this for logging, global state updates, or UI synchronization that must reflect local actions immediately.
|
|
54
|
+
|
|
55
|
+
## 🚀 Basic Usage
|
|
56
|
+
|
|
57
|
+
### 1. Configuration
|
|
58
|
+
|
|
59
|
+
Register the desired implementation provider in your application bootstrap.
|
|
60
|
+
|
|
61
|
+
**For in-process communication (Local):**
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { configureLocalMessageBus } from '@tstdl/base/message-bus';
|
|
65
|
+
|
|
66
|
+
// In your bootstrap/main file
|
|
67
|
+
configureLocalMessageBus();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Injection & Usage
|
|
71
|
+
|
|
72
|
+
Inject the `MessageBus` by passing the channel name as the argument.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { inject } from '@tstdl/base/injector';
|
|
76
|
+
import { MessageBus } from '@tstdl/base/message-bus';
|
|
77
|
+
|
|
78
|
+
// 1. Define your message type
|
|
79
|
+
type UserCreatedEvent = { id: string; email: string };
|
|
80
|
+
|
|
81
|
+
export class UserService {
|
|
82
|
+
// 2. Inject the bus for a specific channel ('user-events')
|
|
83
|
+
private readonly userBus = inject(MessageBus<UserCreatedEvent>, 'user-events');
|
|
84
|
+
|
|
85
|
+
async createUser(email: string) {
|
|
86
|
+
const id = '123';
|
|
87
|
+
// ... creation logic ...
|
|
88
|
+
|
|
89
|
+
// 3. Publish a message
|
|
90
|
+
await this.userBus.publish({ id, email });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class NotificationService {
|
|
95
|
+
private readonly userBus = inject(MessageBus<UserCreatedEvent>, 'user-events');
|
|
96
|
+
|
|
97
|
+
constructor() {
|
|
98
|
+
// 4. Subscribe to messages
|
|
99
|
+
this.userBus.messages$.subscribe((event) => {
|
|
100
|
+
console.log(`New user created: ${event.email}`);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Alternative: Manual Resolution
|
|
107
|
+
|
|
108
|
+
If you're not using dependency injection for a specific class or need to resolve a channel dynamically, use the `MessageBusProvider`.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { inject } from '@tstdl/base/injector';
|
|
112
|
+
import { MessageBusProvider } from '@tstdl/base/message-bus';
|
|
113
|
+
|
|
114
|
+
const provider = inject(MessageBusProvider);
|
|
115
|
+
const bus = provider.get<string>('dynamic-channel');
|
|
116
|
+
|
|
117
|
+
await bus.publish('Hello world');
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🔧 Advanced Topics
|
|
121
|
+
|
|
122
|
+
### Reactive Patterns
|
|
123
|
+
|
|
124
|
+
Since `MessageBus` is built on RxJS, you can use any operators to handle your message streams.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { filter, bufferTime } from 'rxjs';
|
|
128
|
+
|
|
129
|
+
// Only handle specific events
|
|
130
|
+
this.userBus.messages$.pipe(
|
|
131
|
+
filter(event => event.email.endsWith('@example.com'))
|
|
132
|
+
).subscribe(event => { ... });
|
|
133
|
+
|
|
134
|
+
// Batch events for processing
|
|
135
|
+
this.userBus.messages$.pipe(
|
|
136
|
+
bufferTime(1000),
|
|
137
|
+
filter(batch => batch.length > 0)
|
|
138
|
+
).subscribe(batch => {
|
|
139
|
+
console.log(`Processed ${batch.length} events this second.`);
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Cross-Context Communication (BroadcastChannel)
|
|
144
|
+
|
|
145
|
+
To enable communication between different tabs or windows (browsers) or between multiple workers/processes (Node.js), use the `BroadcastChannel` configuration.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { configureBroadcastChannelMessageBus } from '@tstdl/base/message-bus';
|
|
149
|
+
|
|
150
|
+
// Use this instead of configureLocalMessageBus
|
|
151
|
+
configureBroadcastChannelMessageBus();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> **Note:** `BroadcastChannel` is natively supported in modern browsers and Node.js 18+. For older environments, a polyfill may be required.
|
|
155
|
+
|
|
156
|
+
### Custom Injector Configuration
|
|
157
|
+
|
|
158
|
+
When configuring the `LocalMessageBus`, you can optionally specify which injector to use. This is useful in complex setups or when using multiple containers.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { configureLocalMessageBus } from '@tstdl/base/message-bus';
|
|
162
|
+
import { myCustomInjector } from './my-context';
|
|
163
|
+
|
|
164
|
+
configureLocalMessageBus({ injector: myCustomInjector });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Example: Logout Synchronization**
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { inject } from '@tstdl/base/injector';
|
|
171
|
+
import { MessageBus } from '@tstdl/base/message-bus';
|
|
172
|
+
|
|
173
|
+
type AuthEvent = { type: 'logout' };
|
|
174
|
+
|
|
175
|
+
class AuthService {
|
|
176
|
+
private readonly authBus = inject(MessageBus<AuthEvent>, 'auth');
|
|
177
|
+
|
|
178
|
+
async logout() {
|
|
179
|
+
// Perform logout
|
|
180
|
+
await this.authBus.publish({ type: 'logout' });
|
|
181
|
+
window.location.reload();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
listenForExternalLogout() {
|
|
185
|
+
// 'messages$' only emits if *another* tab publishes the event
|
|
186
|
+
this.authBus.messages$.subscribe((event) => {
|
|
187
|
+
if (event.type === 'logout') {
|
|
188
|
+
console.log('Logout triggered in another tab.');
|
|
189
|
+
window.location.reload();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Fire and Forget
|
|
197
|
+
|
|
198
|
+
The standard `publish` method returns a `Promise` that resolves when the message has been dispatched (e.g., posted to the BroadcastChannel). If you do not need to wait for this, use `publishAndForget`.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Non-blocking publish
|
|
202
|
+
this.messageBus.publishAndForget({ type: 'ping' });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Resource Management
|
|
206
|
+
|
|
207
|
+
`MessageBus` implements `AsyncDisposable`. When a bus is no longer needed, it should be disposed to close underlying channels (like `BroadcastChannel`) and complete internal subjects.
|
|
208
|
+
|
|
209
|
+
If you are using the dependency injection system with scoped providers, this is handled automatically. If you create instances manually or hold them in long-lived services, ensure you dispose of them.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
await bus[Symbol.asyncDispose]();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 📚 API
|
|
216
|
+
|
|
217
|
+
### Classes & Interfaces
|
|
218
|
+
|
|
219
|
+
| Name | Description |
|
|
220
|
+
| :------------------------------------ | :---------------------------------------------------- |
|
|
221
|
+
| `MessageBus<T>` | Abstract base class for a message bus instance. |
|
|
222
|
+
| `MessageBusBase<T>` | Helper class for implementing custom message buses. |
|
|
223
|
+
| `MessageBusProvider` | Abstract factory for creating `MessageBus` instances. |
|
|
224
|
+
| `LocalMessageBus<T>` | Implementation for in-process communication. |
|
|
225
|
+
| `LocalMessageBusProvider` | Provider implementation for `LocalMessageBus`. |
|
|
226
|
+
| `BroadcastChannelMessageBus<T>` | Implementation for cross-context communication. |
|
|
227
|
+
| `BroadcastChannelMessageBusProvider` | Provider implementation for `BroadcastChannelMessageBus`. |
|
|
228
|
+
|
|
229
|
+
### `MessageBus<T>` Members
|
|
230
|
+
|
|
231
|
+
| Member | Type | Description |
|
|
232
|
+
| :-------------------------- | :-------------- | :------------------------------------------- |
|
|
233
|
+
| `messages$` | `Observable<T>` | Stream of messages from **other** instances. |
|
|
234
|
+
| `allMessages$` | `Observable<T>` | Stream of **all** messages (including self). |
|
|
235
|
+
| `publish(message)` | `Promise<void>` | Publishes a message asynchronously. |
|
|
236
|
+
| `publishAndForget(message)` | `void` | Publishes a message without waiting. |
|
|
237
|
+
| `dispose()` | `Promise<void>` | Manually disposes the bus. |
|
|
238
|
+
|
|
239
|
+
### Configuration Functions
|
|
240
|
+
|
|
241
|
+
| Function | Description |
|
|
242
|
+
| :-------------------------------------- | :-------------------------------------------------------------------- |
|
|
243
|
+
| `configureLocalMessageBus()` | Registers `LocalMessageBus` as the default implementation. |
|
|
244
|
+
| `configureBroadcastChannelMessageBus()` | Registers `BroadcastChannelMessageBus` as the default implementation. |
|
|
@@ -13,7 +13,7 @@ export class MessageBusBase extends MessageBus {
|
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
this.publishSubject = new Subject();
|
|
15
15
|
this.disposeToken = new CancellationToken();
|
|
16
|
-
this.messages$ = defer(() => this._messages$).pipe(takeUntil(this.disposeToken
|
|
16
|
+
this.messages$ = defer(() => this._messages$).pipe(takeUntil(this.disposeToken), share());
|
|
17
17
|
this.allMessages$ = merge(this.messages$, this.publishSubject);
|
|
18
18
|
}
|
|
19
19
|
publishAndForget(message) {
|
package/module/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Application Modules
|
|
2
|
+
|
|
3
|
+
The `@tstdl/base/module` library provides a robust framework for building modular applications with managed lifecycles. It defines a standard contract for components that need to be started, monitored, and gracefully stopped, such as background workers, API servers, or custom services.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [The Module Class](#the-module-class)
|
|
10
|
+
- [Lifecycle States](#lifecycle-states)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [Creating a Custom Module](#creating-a-custom-module)
|
|
13
|
+
- [Manual Execution](#manual-execution)
|
|
14
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
15
|
+
- [FunctionModule](#functionmodule)
|
|
16
|
+
- [WebServerModule](#webservermodule)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Standardized Lifecycle**: Uniform `run()` and `stop()` methods for all application components.
|
|
22
|
+
- **State Management**: Built-in tracking of module states (`Running`, `Stopping`, `Stopped`, `Erroneous`).
|
|
23
|
+
- **Graceful Shutdown**: Integrated `CancellationSignal` support for clean resource cleanup.
|
|
24
|
+
- **Resource Management**: Implemented `AsyncDisposable` for automatic cleanup when using the `using` keyword.
|
|
25
|
+
- **Ready-to-Use Modules**: Includes pre-built modules for common tasks like running functions or web servers.
|
|
26
|
+
- **Type Safety**: Fully typed with TypeScript for reliable development.
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### The Module Class
|
|
31
|
+
|
|
32
|
+
The abstract `Module` class is the foundation of this library. It handles the internal state machine and synchronization required to start and stop services safely.
|
|
33
|
+
|
|
34
|
+
When you extend `Module`, you implement a single protected method: `_run(cancellationSignal)`. This method contains your main logic. The base class ensures that `_run` is called only when the module is stopped and handles the propagation of the cancellation signal when `stop()` is called.
|
|
35
|
+
|
|
36
|
+
### Lifecycle States
|
|
37
|
+
|
|
38
|
+
A module is always in one of the following states, defined by the `ModuleState` enum:
|
|
39
|
+
|
|
40
|
+
1. **Stopped**: The initial state. The module is ready to run.
|
|
41
|
+
2. **Running**: The `_run` method is currently executing.
|
|
42
|
+
3. **Stopping**: A stop has been requested, and the cancellation signal has been triggered, but the `_run` promise has not yet resolved.
|
|
43
|
+
4. **Erroneous**: The module threw an unhandled exception during execution.
|
|
44
|
+
|
|
45
|
+
## 🚀 Basic Usage
|
|
46
|
+
|
|
47
|
+
### Creating a Custom Module
|
|
48
|
+
|
|
49
|
+
To create a module, extend the `Module` class and implement the `_run` method. Use the provided `CancellationSignal` to detect when the module should stop.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { CancellationSignal } from '@tstdl/base/cancellation';
|
|
53
|
+
import { Module } from '@tstdl/base/module';
|
|
54
|
+
import { cancelableTimeout } from '@tstdl/base/utils';
|
|
55
|
+
|
|
56
|
+
export class DataProcessorModule extends Module {
|
|
57
|
+
constructor() {
|
|
58
|
+
super('DataProcessor');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected async _run(cancellationSignal: CancellationSignal): Promise<void> {
|
|
62
|
+
console.log('DataProcessor started.');
|
|
63
|
+
|
|
64
|
+
// Run a loop until the signal is set (stop requested)
|
|
65
|
+
while (cancellationSignal.isUnset) {
|
|
66
|
+
console.log('Processing data batch...');
|
|
67
|
+
|
|
68
|
+
// Simulate work that can be cancelled
|
|
69
|
+
await cancelableTimeout(1000, cancellationSignal);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('DataProcessor stopping gracefully.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Manual Execution
|
|
78
|
+
|
|
79
|
+
While modules are often managed by an `Application` runner (from `@tstdl/base/application`), you can run them manually.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { DataProcessorModule } from './data-processor.module.js';
|
|
83
|
+
import { timeout } from '@tstdl/base/utils';
|
|
84
|
+
|
|
85
|
+
async function main() {
|
|
86
|
+
const module = new DataProcessorModule();
|
|
87
|
+
|
|
88
|
+
// Start the module (non-blocking)
|
|
89
|
+
const runPromise = module.run();
|
|
90
|
+
|
|
91
|
+
console.log(`Module state: ${module.state}`); // Running
|
|
92
|
+
|
|
93
|
+
// Let it run for a bit
|
|
94
|
+
await timeout(3000);
|
|
95
|
+
|
|
96
|
+
// Stop the module
|
|
97
|
+
console.log('Stopping module...');
|
|
98
|
+
await module.stop();
|
|
99
|
+
|
|
100
|
+
// Wait for the run promise to complete to ensure cleanup is done
|
|
101
|
+
await runPromise;
|
|
102
|
+
|
|
103
|
+
console.log(`Module state: ${module.state}`); // Stopped
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
main();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 🔧 Advanced Topics
|
|
110
|
+
|
|
111
|
+
### FunctionModule
|
|
112
|
+
|
|
113
|
+
The `FunctionModule` is a generic wrapper that allows you to treat a simple asynchronous function as a full-fledged module. This is useful for simple background tasks or scripts that don't require a dedicated class.
|
|
114
|
+
|
|
115
|
+
It is typically used in conjunction with the dependency injection system or the `Application` runner (from `@tstdl/base/application`), which handles the instantiation and argument injection.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { Application, provideModule } from '@tstdl/base/application';
|
|
119
|
+
import { CancellationSignal } from '@tstdl/base/cancellation';
|
|
120
|
+
|
|
121
|
+
async function myTask(signal: CancellationSignal) {
|
|
122
|
+
console.log('Function module running');
|
|
123
|
+
|
|
124
|
+
while (signal.isUnset) {
|
|
125
|
+
// ... logic
|
|
126
|
+
await timeout(1000);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run the task as a module within an application
|
|
131
|
+
Application.run('MyApplication', [
|
|
132
|
+
provideModule(myTask)
|
|
133
|
+
]);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### WebServerModule
|
|
137
|
+
|
|
138
|
+
The `WebServerModule` is a specialized module that integrates with `@tstdl/base/http` and `@tstdl/base/api` to run an HTTP server. It automatically registers API controllers and handles the server lifecycle.
|
|
139
|
+
|
|
140
|
+
It is designed to be used with the Dependency Injection system.
|
|
141
|
+
|
|
142
|
+
**Configuration:**
|
|
143
|
+
|
|
144
|
+
You can configure the port using `configureWebServerModule`.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { configureWebServerModule, WebServerModule } from '@tstdl/base/module';
|
|
148
|
+
import { Application, provideModule } from '@tstdl/base/application';
|
|
149
|
+
import { configureNodeHttpServer } from '@tstdl/base/http/node';
|
|
150
|
+
import { configureApiServer } from '@tstdl/base/api/server';
|
|
151
|
+
|
|
152
|
+
// 1. Configure the environment
|
|
153
|
+
function bootstrap() {
|
|
154
|
+
configureNodeHttpServer(); // Select Node.js HTTP implementation
|
|
155
|
+
configureApiServer({
|
|
156
|
+
controllers: [
|
|
157
|
+
/* ... your controllers ... */
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Configure the WebServerModule specific settings
|
|
162
|
+
configureWebServerModule({ port: 8080 });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 2. Run the application with the WebServerModule
|
|
166
|
+
Application.run('MyApiServer', [
|
|
167
|
+
provideModule(WebServerModule),
|
|
168
|
+
// ... other providers
|
|
169
|
+
]);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 📚 API
|
|
173
|
+
|
|
174
|
+
| Type | Name | Description |
|
|
175
|
+
| :----------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| **Class** | `Module` | Abstract base class for all modules. Handles state transitions and cancellation signals. Also implements `AsyncDisposable`. |
|
|
177
|
+
| **Enum** | `ModuleState` | Enumeration of module states: `Running`, `Stopping`, `Stopped`, `Erroneous`. |
|
|
178
|
+
| **Class** | `FunctionModule` | A generic module implementation that executes a provided function. |
|
|
179
|
+
| **Type** | `FunctionModuleFunction` | Signature for functions used with `FunctionModule`: `(signal: CancellationSignal) => void \| Promise<void>`. |
|
|
180
|
+
| **Class** | `WebServerModule` | A module that runs an HTTP server and serves registered API controllers. |
|
|
181
|
+
| **Type** | `WebServerModuleConfiguration` | Configuration object for `WebServerModule` (e.g., `{ port: number }`). |
|
|
182
|
+
| **Function** | `configureWebServerModule` | Helper function to set the global configuration for the `WebServerModule`. |
|
package/module/module.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ModuleState = EnumType<typeof ModuleState>;
|
|
|
11
11
|
export declare abstract class Module implements AsyncDisposable {
|
|
12
12
|
private runPromise;
|
|
13
13
|
private _state;
|
|
14
|
-
protected
|
|
14
|
+
protected cancellationToken: CancellationToken;
|
|
15
15
|
readonly name: string;
|
|
16
16
|
get state(): ModuleState;
|
|
17
17
|
private get stateString();
|
package/module/module.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { CancellationToken } from '../cancellation/index.js';
|
|
2
54
|
import { defineEnum } from '../enumeration/enumeration.js';
|
|
3
55
|
import { enumValueName } from '../utils/enum.js';
|
|
@@ -8,8 +60,8 @@ export const ModuleState = defineEnum('ModuleState', {
|
|
|
8
60
|
Erroneous: 3,
|
|
9
61
|
});
|
|
10
62
|
export class Module {
|
|
11
|
-
runPromise;
|
|
12
|
-
_state;
|
|
63
|
+
runPromise = Promise.resolve();
|
|
64
|
+
_state = ModuleState.Stopped;
|
|
13
65
|
cancellationToken;
|
|
14
66
|
name;
|
|
15
67
|
get state() {
|
|
@@ -20,27 +72,35 @@ export class Module {
|
|
|
20
72
|
}
|
|
21
73
|
constructor(name) {
|
|
22
74
|
this.name = name;
|
|
23
|
-
this.runPromise = Promise.resolve();
|
|
24
|
-
this._state = ModuleState.Stopped;
|
|
25
|
-
this.cancellationToken = new CancellationToken();
|
|
26
75
|
}
|
|
27
76
|
async [Symbol.asyncDispose]() {
|
|
28
77
|
await this.stop();
|
|
29
78
|
}
|
|
30
79
|
async run() {
|
|
31
|
-
|
|
32
|
-
throw new Error(`cannot start module, it is ${this.stateString}`);
|
|
33
|
-
}
|
|
34
|
-
this.cancellationToken.unset();
|
|
80
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
35
81
|
try {
|
|
36
|
-
this._state
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
if (this._state != ModuleState.Stopped) {
|
|
83
|
+
throw new Error(`cannot start module, it is ${this.stateString}`);
|
|
84
|
+
}
|
|
85
|
+
const cancellationToken = __addDisposableResource(env_1, new CancellationToken(), false);
|
|
86
|
+
this.cancellationToken = cancellationToken;
|
|
87
|
+
try {
|
|
88
|
+
this._state = ModuleState.Running;
|
|
89
|
+
this.runPromise = this._run(cancellationToken);
|
|
90
|
+
await this.runPromise;
|
|
91
|
+
this._state = ModuleState.Stopped;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this._state = ModuleState.Erroneous;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e_1) {
|
|
99
|
+
env_1.error = e_1;
|
|
100
|
+
env_1.hasError = true;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
__disposeResources(env_1);
|
|
44
104
|
}
|
|
45
105
|
}
|
|
46
106
|
async stop() {
|
|
@@ -38,7 +38,7 @@ let WebServerModule = class WebServerModule extends Module {
|
|
|
38
38
|
async _run(cancellationSignal) {
|
|
39
39
|
this.initialize();
|
|
40
40
|
await this.httpServer.listen(this.config.port ?? 8000);
|
|
41
|
-
const closePromise = cancellationSignal
|
|
41
|
+
const closePromise = cancellationSignal.wait().then(async () => {
|
|
42
42
|
await this.httpServer[Symbol.asyncDispose]();
|
|
43
43
|
});
|
|
44
44
|
for await (const context of this.httpServer) {
|
|
@@ -1,35 +1,44 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
2
2
|
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
3
|
import { setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
4
4
|
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
5
5
|
describe('NotificationTypeService', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
let injector;
|
|
7
|
+
let database;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
({ injector, database } = await setupIntegrationTest({ modules: { notification: true, authentication: true } }));
|
|
9
10
|
await truncateTables(database, 'notification', ['type']);
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await injector.dispose();
|
|
14
|
+
});
|
|
15
|
+
test('should initialize types correctly', async () => {
|
|
10
16
|
const service = injector.resolve(NotificationTypeService);
|
|
17
|
+
const prefix = crypto.randomUUID();
|
|
18
|
+
const type1 = `${prefix}_TYPE1`;
|
|
19
|
+
const type2 = `${prefix}_TYPE2`;
|
|
11
20
|
const typeData = {
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
[type1]: { label: 'Type 1' },
|
|
22
|
+
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
|
|
14
23
|
};
|
|
15
24
|
await runInInjectionContext(injector, async () => {
|
|
16
25
|
const result = await service.initializeTypes(typeData);
|
|
17
|
-
expect(result
|
|
18
|
-
expect(result
|
|
19
|
-
expect(result
|
|
26
|
+
expect(result[type1]?.label).toBe('Type 1');
|
|
27
|
+
expect(result[type2]?.key).toBe(type2);
|
|
28
|
+
expect(result[type2]?.throttling?.limit).toBe(1);
|
|
20
29
|
// Verify persistence
|
|
21
|
-
const dbTypes = await service.repository.
|
|
30
|
+
const dbTypes = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
22
31
|
expect(dbTypes).toHaveLength(2);
|
|
23
32
|
// Update
|
|
24
33
|
const updatedData = {
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
[type1]: { label: 'Type 1 Updated' },
|
|
35
|
+
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
|
|
27
36
|
};
|
|
28
37
|
const resultUpdated = await service.initializeTypes(updatedData);
|
|
29
|
-
expect(resultUpdated
|
|
30
|
-
const dbTypesUpdated = await service.repository.
|
|
38
|
+
expect(resultUpdated[type1]?.label).toBe('Type 1 Updated');
|
|
39
|
+
const dbTypesUpdated = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
31
40
|
expect(dbTypesUpdated).toHaveLength(2);
|
|
32
|
-
expect(dbTypesUpdated.find((c) => c.key ==
|
|
41
|
+
expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
|
|
33
42
|
});
|
|
34
43
|
});
|
|
35
44
|
});
|