@tstdl/base 0.93.139 → 0.93.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.js +2 -2
  28. package/audit/README.md +267 -0
  29. package/authentication/README.md +288 -0
  30. package/authentication/client/authentication.service.d.ts +12 -11
  31. package/authentication/client/authentication.service.js +21 -21
  32. package/authentication/client/http-client.middleware.js +2 -2
  33. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  34. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  35. package/browser/README.md +401 -0
  36. package/cancellation/README.md +156 -0
  37. package/cancellation/tests/coverage.test.d.ts +1 -0
  38. package/cancellation/tests/coverage.test.js +49 -0
  39. package/cancellation/tests/leak.test.js +24 -29
  40. package/cancellation/tests/token.test.d.ts +1 -0
  41. package/cancellation/tests/token.test.js +136 -0
  42. package/cancellation/token.d.ts +53 -177
  43. package/cancellation/token.js +132 -208
  44. package/context/README.md +174 -0
  45. package/cookie/README.md +161 -0
  46. package/css/README.md +157 -0
  47. package/data-structures/README.md +320 -0
  48. package/decorators/README.md +140 -0
  49. package/distributed-loop/README.md +231 -0
  50. package/distributed-loop/distributed-loop.js +1 -1
  51. package/document-management/README.md +403 -0
  52. package/document-management/server/services/document-management.service.js +9 -7
  53. package/document-management/tests/document-management-core.test.js +2 -7
  54. package/document-management/tests/document-management.api.test.js +6 -7
  55. package/document-management/tests/document-statistics.service.test.js +11 -12
  56. package/document-management/tests/document.service.test.js +3 -3
  57. package/document-management/tests/enum-helpers.test.js +2 -3
  58. package/dom/README.md +213 -0
  59. package/enumerable/README.md +259 -0
  60. package/enumeration/README.md +121 -0
  61. package/errors/README.md +267 -0
  62. package/file/README.md +191 -0
  63. package/formats/README.md +210 -0
  64. package/function/README.md +144 -0
  65. package/http/README.md +318 -0
  66. package/http/client/adapters/undici.adapter.js +1 -1
  67. package/http/client/http-client-request.d.ts +6 -5
  68. package/http/client/http-client-request.js +8 -9
  69. package/http/server/node/node-http-server.js +1 -2
  70. package/image-service/README.md +137 -0
  71. package/injector/README.md +491 -0
  72. package/intl/README.md +113 -0
  73. package/json-path/README.md +182 -0
  74. package/jsx/README.md +154 -0
  75. package/key-value-store/README.md +191 -0
  76. package/lock/README.md +249 -0
  77. package/lock/web/web-lock.js +119 -47
  78. package/logger/README.md +287 -0
  79. package/mail/README.md +256 -0
  80. package/memory/README.md +144 -0
  81. package/message-bus/README.md +244 -0
  82. package/message-bus/message-bus-base.js +1 -1
  83. package/module/README.md +182 -0
  84. package/module/module.d.ts +1 -1
  85. package/module/module.js +77 -17
  86. package/module/modules/web-server.module.js +1 -1
  87. package/notification/tests/notification-type.service.test.js +24 -15
  88. package/object-storage/README.md +300 -0
  89. package/openid-connect/README.md +274 -0
  90. package/orm/README.md +423 -0
  91. package/package.json +8 -6
  92. package/password/README.md +164 -0
  93. package/pdf/README.md +246 -0
  94. package/polyfills.js +1 -0
  95. package/pool/README.md +198 -0
  96. package/process/README.md +237 -0
  97. package/promise/README.md +252 -0
  98. package/promise/cancelable-promise.js +1 -1
  99. package/random/README.md +193 -0
  100. package/reflection/README.md +305 -0
  101. package/rpc/README.md +386 -0
  102. package/rxjs-utils/README.md +262 -0
  103. package/schema/README.md +342 -0
  104. package/serializer/README.md +342 -0
  105. package/signals/implementation/README.md +134 -0
  106. package/sse/README.md +278 -0
  107. package/task-queue/README.md +300 -0
  108. package/task-queue/postgres/task-queue.d.ts +2 -1
  109. package/task-queue/postgres/task-queue.js +32 -2
  110. package/task-queue/task-context.js +1 -1
  111. package/task-queue/task-queue.d.ts +17 -0
  112. package/task-queue/task-queue.js +103 -45
  113. package/task-queue/tests/complex.test.js +4 -4
  114. package/task-queue/tests/dependencies.test.js +4 -2
  115. package/task-queue/tests/queue.test.js +111 -0
  116. package/task-queue/tests/worker.test.js +21 -13
  117. package/templates/README.md +287 -0
  118. package/testing/README.md +157 -0
  119. package/text/README.md +346 -0
  120. package/threading/README.md +238 -0
  121. package/types/README.md +311 -0
  122. package/utils/README.md +322 -0
  123. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  124. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  125. package/utils/async-iterable-helpers/take-until.js +4 -4
  126. package/utils/backoff.js +89 -30
  127. package/utils/retry-with-backoff.js +1 -1
  128. package/utils/timer.d.ts +1 -1
  129. package/utils/timer.js +5 -7
  130. package/utils/timing.d.ts +1 -1
  131. package/utils/timing.js +2 -4
  132. package/utils/z-base32.d.ts +1 -0
  133. package/utils/z-base32.js +1 -0
@@ -0,0 +1,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.set$), share());
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) {
@@ -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`. |
@@ -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 readonly cancellationToken: CancellationToken;
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
- if (this._state != ModuleState.Stopped) {
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 = ModuleState.Running;
37
- this.runPromise = this._run(this.cancellationToken);
38
- await this.runPromise;
39
- this._state = ModuleState.Stopped;
40
- }
41
- catch (error) {
42
- this._state = ModuleState.Erroneous;
43
- throw error;
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.$set.then(async () => {
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
- test('should initialize types correctly', async () => {
7
- const { injector, database } = await setupIntegrationTest({ modules: { notification: true, authentication: true } });
8
- // Cleanup
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
- TYPE1: { label: 'Type 1' },
13
- TYPE2: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
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.TYPE1.label).toBe('Type 1');
18
- expect(result.TYPE2.key).toBe('TYPE2');
19
- expect(result.TYPE2.throttling?.limit).toBe(1);
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.loadAll();
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
- TYPE1: { label: 'Type 1 Updated' },
26
- TYPE2: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
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.TYPE1.label).toBe('Type 1 Updated');
30
- const dbTypesUpdated = await service.repository.loadAll();
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 == 'TYPE1')?.label).toBe('Type 1 Updated');
41
+ expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
33
42
  });
34
43
  });
35
44
  });