@tstdl/base 0.93.139 → 0.93.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
package/api/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Observable } from 'rxjs';
2
2
  import type { Auditor } from '../audit/index.js';
3
3
  import type { Token } from '../authentication/index.js';
4
+ import type { CancellationSource } from '../cancellation/token.js';
4
5
  import type { HttpHeaders, HttpHeadersObject, HttpRequestAuthorization } from '../http/index.js';
5
6
  import type { HttpServerRequest, HttpServerResponse } from '../http/server/index.js';
6
7
  import type { HttpMethod } from '../http/types.js';
@@ -132,9 +133,9 @@ export type ApiRequestContext<T extends ApiDefinition = ApiDefinition, K extends
132
133
  export type ApiEndpointServerImplementation<T extends ApiDefinition = ApiDefinition, K extends ApiEndpointKeys<T> = ApiEndpointKeys<T>> = (context: ApiRequestContext<T, K>) => ApiServerResult<T, K> | Promise<ApiServerResult<T, K>>;
133
134
  export type ApiClientRequestOptions = {
134
135
  /**
135
- * AbortSignal to cancel the request.
136
+ * CancellationSignal to cancel the request.
136
137
  */
137
- abortSignal?: AbortSignal;
138
+ cancellationSignal?: CancellationSource;
138
139
  /**
139
140
  * Request timeout in milliseconds.
140
141
  * @default 30000
@@ -0,0 +1,240 @@
1
+ # Application Module
2
+
3
+ A robust framework for bootstrapping, managing lifecycle, and handling graceful shutdown of TypeScript applications. It integrates seamlessly with dependency injection to orchestrate modules, services, and startup/cleanup routines.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [The Application Container](#the-application-container)
10
+ - [Modules](#modules)
11
+ - [Lifecycle Hooks](#lifecycle-hooks)
12
+ - [Graceful Shutdown](#graceful-shutdown)
13
+ - [🚀 Basic Usage](#-basic-usage)
14
+ - [🔧 Advanced Topics](#-advanced-topics)
15
+ - [Class-Based Modules](#class-based-modules)
16
+ - [Initializers and Destructors](#initializers-and-destructors)
17
+ - [Programmatic Shutdown](#programmatic-shutdown)
18
+ - [📚 API](#-api)
19
+
20
+ ## ✨ Features
21
+
22
+ - **Modular Architecture**: Organize application logic into reusable, independent modules.
23
+ - **Dependency Injection**: Built on top of `@tstdl/base/injector` for robust service resolution.
24
+ - **Lifecycle Management**: Orchestrates the startup and shutdown sequence of all registered modules.
25
+ - **Graceful Shutdown**: Built-in handling of process signals (like `SIGINT` and `SIGTERM`) to ensure resources are released properly.
26
+ - **Async Hooks**: Supports asynchronous initializers and destructors for database connections or external service setup.
27
+ - **Cancellation Propagation**: Provides a `CancellationSignal` to all modules to coordinate termination of long-running tasks.
28
+
29
+ ## Core Concepts
30
+
31
+ ### The Application Container
32
+
33
+ The `Application` class is the entry point. It creates a dependency injection container, resolves all registered modules, and manages their execution state. It acts as the central hub for the application's lifecycle.
34
+
35
+ ### Modules
36
+
37
+ A module is a unit of execution. It can be:
38
+
39
+ - **A simple function**: Useful for scripts or simple entry points.
40
+ - **A class extending `Module`**: Useful for complex, stateful services that need to handle startup and shutdown logic explicitly.
41
+
42
+ ### Lifecycle Hooks
43
+
44
+ - **Initializers**: Functions that run _before_ any module is started. Used for configuration, database connections, or pre-flight checks.
45
+ - **Destructors**: Functions that run _after_ all modules have stopped. Used for closing connections, flushing logs, or cleaning up resources.
46
+
47
+ ### Graceful Shutdown
48
+
49
+ When configured with `provideSignalHandler()`, the application listens for termination signals. Upon receiving one:
50
+
51
+ 1. The global `shutdownSignal` is triggered.
52
+ 2. All running modules are requested to stop.
53
+ 3. The application waits for modules to finish their cleanup.
54
+ 4. Destructors are executed.
55
+ 5. The process exits.
56
+
57
+ ## 🚀 Basic Usage
58
+
59
+ The simplest way to start an application is using a function as a module.
60
+
61
+ ```ts
62
+ import { Application, provideModule, provideSignalHandler } from '@tstdl/base/application';
63
+ import { inject } from '@tstdl/base/injector';
64
+ import { Logger } from '@tstdl/base/logger';
65
+ import { timeout } from '@tstdl/base/utils';
66
+
67
+ // 1. Define your main logic
68
+ async function main(): Promise<void> {
69
+ const logger = inject(Logger);
70
+ const app = inject(Application);
71
+
72
+ logger.info('Application started!');
73
+
74
+ // Simulate some work
75
+ await timeout(1000);
76
+
77
+ logger.info('Work done.');
78
+
79
+ // Request shutdown if the task is finite
80
+ app.requestShutdown();
81
+ }
82
+
83
+ // 2. Bootstrap the application
84
+ Application.run('MyBasicApp', [
85
+ provideModule(main),
86
+ provideSignalHandler(), // Handles Ctrl+C (SIGINT) automatically
87
+ ]);
88
+ ```
89
+
90
+ ## 🔧 Advanced Topics
91
+
92
+ ### Class-Based Modules
93
+
94
+ For long-running services or complex logic, define a class extending `Module`. This allows you to access the `CancellationSignal` to handle shutdown requests gracefully within your loops.
95
+
96
+ ```ts
97
+ import { Application, provideModule, provideSignalHandler } from '@tstdl/base/application';
98
+ import { CancellationSignal } from '@tstdl/base/cancellation';
99
+ import { inject, Singleton } from '@tstdl/base/injector';
100
+ import { Logger } from '@tstdl/base/logger';
101
+ import { Module } from '@tstdl/base/module';
102
+ import { cancelableTimeout } from '@tstdl/base/utils';
103
+
104
+ @Singleton()
105
+ export class WorkerModule extends Module {
106
+ readonly #logger = inject(Logger, WorkerModule.name);
107
+
108
+ constructor() {
109
+ super('Worker');
110
+ }
111
+
112
+ // The _run method is called when the application starts
113
+ protected override async _run(cancellationSignal: CancellationSignal): Promise<void> {
114
+ this.#logger.info('Worker started.');
115
+
116
+ // Loop until shutdown is requested
117
+ while (cancellationSignal.isUnset) {
118
+ this.#logger.info('Processing job...');
119
+
120
+ // Use cancelableTimeout to respect shutdown signals immediately during waits
121
+ const result = await cancelableTimeout(2000, cancellationSignal);
122
+
123
+ if (result == 'canceled') {
124
+ break;
125
+ }
126
+ }
127
+
128
+ this.#logger.info('Worker stopped gracefully.');
129
+ }
130
+ }
131
+
132
+ Application.run('WorkerApp', [provideModule(WorkerModule), provideSignalHandler()]);
133
+ ```
134
+
135
+ ### Initializers and Destructors
136
+
137
+ Use these hooks to manage global resources like database connections.
138
+
139
+ ```ts
140
+ import { Application, provideInitializer, provideDestructor, provideModule, provideSignalHandler } from '@tstdl/base/application';
141
+ import { inject, injectAsync, Singleton } from '@tstdl/base/injector';
142
+ import { Logger } from '@tstdl/base/logger';
143
+
144
+ @Singleton()
145
+ class DatabaseService {
146
+ readonly #logger = inject(Logger, 'Database');
147
+
148
+ async connect() {
149
+ this.#logger.info('Connected to DB');
150
+ }
151
+ async disconnect() {
152
+ this.#logger.info('Disconnected from DB');
153
+ }
154
+ }
155
+
156
+ // Initializer: Runs BEFORE modules start
157
+ const initDb = async () => {
158
+ const db = await injectAsync(DatabaseService);
159
+ await db.connect();
160
+ };
161
+
162
+ // Destructor: Runs AFTER modules stop
163
+ const closeDb = async () => {
164
+ const db = await injectAsync(DatabaseService);
165
+ await db.disconnect();
166
+ };
167
+
168
+ async function main() {
169
+ const logger = inject(Logger);
170
+ logger.info('App logic running with DB connection available.');
171
+ }
172
+
173
+ Application.run('DbApp', [provideInitializer(initDb), provideModule(main), provideDestructor(closeDb), provideSignalHandler()]);
174
+ ```
175
+
176
+ ### Programmatic Shutdown
177
+
178
+ You can trigger the shutdown sequence from anywhere in your application by injecting the `Application` instance.
179
+
180
+ ```ts
181
+ import { Application, provideModule } from '@tstdl/base/application';
182
+ import { inject } from '@tstdl/base/injector';
183
+
184
+ async function main() {
185
+ const app = inject(Application);
186
+
187
+ try {
188
+ // ... do critical work
189
+ throw new Error('Critical failure');
190
+ } catch (error) {
191
+ console.error(error);
192
+ // Trigger graceful shutdown
193
+ app.requestShutdown();
194
+ }
195
+ }
196
+
197
+ Application.run('ShutdownApp', [provideModule(main)]);
198
+ ```
199
+
200
+ ## 📚 API
201
+
202
+ ### Application Class
203
+
204
+ The singleton instance managing the application lifecycle.
205
+
206
+ | Property / Method | Type | Description |
207
+ | :------------------ | :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
208
+ | `static create()` | `(name: string, providers: (ProvidersItem \| ProvidersItem[])[]) => Application` | Creates an application instance without starting it. |
209
+ | `static run()` | `(name: string, providers: (ProvidersItem \| ProvidersItem[])[]) => Application` | Creates and immediately runs the application. |
210
+ | `shutdownSignal` | `CancellationSignal` | The signal that indicates if the application is shutting down. |
211
+ | `run()` | `() => void` | Starts the application lifecycle (initializers -> modules). |
212
+ | `runAndWait()` | `() => Promise<void>` | Starts the application and returns a promise that resolves only after shutdown is complete. |
213
+ | `requestShutdown()` | `() => void` | Signals the application to stop. Sets `shutdownSignal` and begins stopping modules. |
214
+ | `shutdown()` | `() => Promise<void>` | Requests shutdown and waits for the entire teardown process to complete. |
215
+ | `waitForShutdown()` | `() => Promise<void>` | Returns a promise that resolves when the application has fully stopped. |
216
+ | `registerModule()` | `(moduleType: Module \| Type<Module>) => void` | Dynamically registers a module (mostly used internally or for advanced use cases). |
217
+
218
+ ### Provider Functions
219
+
220
+ Helper functions to configure the application in `Application.run()`.
221
+
222
+ | Function | Description |
223
+ | :------------------------------ | :-------------------------------------------------------------------------------- |
224
+ | `provideModule(...modules)` | Registers one or more modules (functions or classes) to be run. |
225
+ | `provideInitializer(fn)` | Registers a function to run before modules start. |
226
+ | `provideDestructor(fn)` | Registers a function to run after modules stop. |
227
+ | `provideSignalHandler()` | Registers listeners for `SIGINT`, `SIGTERM`, etc., to trigger graceful shutdown. |
228
+ | `provideShutdownSignal(signal)` | Provides an external `CancellationSignal` to control the application's lifecycle. |
229
+
230
+ ### Injection Tokens
231
+
232
+ Tokens used to resolve application-specific values.
233
+
234
+ | Token | Type | Description |
235
+ | :------------------------ | :-------------- | :-------------------------------------------------- |
236
+ | `APPLICATION_NAME` | `string` | The name of the application passed to `run()`. |
237
+ | `APPLICATION_INSTANCE_ID` | `string` | A unique UUID for the running application instance. |
238
+ | `APPLICATION_MODULE` | `Module` | Used internally to collect registered modules. |
239
+ | `APPLICATION_INITIALIZER` | `InitializerFn` | Used internally to collect initializers. |
240
+ | `APPLICATION_DESTRUCTOR` | `DestructorFn` | Used internally to collect destructors. |
@@ -4,7 +4,7 @@ import { Module } from '../module/index.js';
4
4
  import type { OneOrMany, Type } from '../types/index.js';
5
5
  export type InitializerFn = () => void | Promise<void>;
6
6
  export type DestructorFn = () => void | Promise<void>;
7
- export declare const APPLICATION_MODULE: import("../injector/token.js").InjectionToken<Module, never>;
7
+ export declare const APPLICATION_MODULE: import("../injector/token.js").InjectionToken<Module | Type<Module>, never>;
8
8
  export declare const APPLICATION_INITIALIZER: import("../injector/token.js").InjectionToken<InitializerFn, never>;
9
9
  export declare const APPLICATION_DESTRUCTOR: import("../injector/token.js").InjectionToken<DestructorFn, never>;
10
10
  export declare const APPLICATION_INSTANCE_ID: import("../injector/token.js").InjectionToken<string, never>;
@@ -27,7 +27,7 @@ let Application = Application_1 = class Application {
27
27
  #logger = inject(Logger, this.#name);
28
28
  #moduleTypesAndInstances = new Set(injectAll(APPLICATION_MODULE, undefined, { optional: true }));
29
29
  #shutdownPromise = new DeferredPromise();
30
- #shutdownToken = inject(CancellationSignal, undefined, { optional: true })?.createChild() ?? new CancellationToken();
30
+ #shutdownToken = new CancellationToken(inject(CancellationSignal, undefined, { optional: true }));
31
31
  get injector() {
32
32
  return this.#injector;
33
33
  }
@@ -79,12 +79,12 @@ let Application = Application_1 = class Application {
79
79
  try {
80
80
  const initializers = await this.#injector.resolveAllAsync(APPLICATION_INITIALIZER, undefined, { optional: true });
81
81
  for (const initializer of initializers) {
82
- await runInInjectionContext(this.#injector, initializer);
82
+ await runInInjectionContext(this.#injector, async () => await initializer());
83
83
  }
84
84
  modules = await toArrayAsync(mapAsync(this.#moduleTypesAndInstances, async (instanceOrType) => (isFunction(instanceOrType) ? await this.#injector.resolveAsync(instanceOrType) : instanceOrType)));
85
85
  await Promise.race([
86
86
  this.runModules(modules),
87
- this.shutdownSignal,
87
+ this.shutdownSignal.wait(),
88
88
  ]);
89
89
  }
90
90
  catch (error) {
@@ -1,9 +1,27 @@
1
1
  import { CancellationSignal } from '../cancellation/index.js';
2
- import { type ProvidersItem } from '../injector/index.js';
2
+ import { Injector, type ProvidersItem } from '../injector/index.js';
3
3
  import { Module, type FunctionModuleFunction } from '../module/index.js';
4
4
  import type { OneOrMany, Type } from '../types/index.js';
5
5
  import { type DestructorFn, type InitializerFn } from './application.js';
6
- export declare function provideModule(...functionsAndModules: OneOrMany<FunctionModuleFunction | Type<Module>>[]): ProvidersItem[];
6
+ /**
7
+ * Registers an application initializer function that will be executed during application startup.
8
+ */
9
+ export declare function registerInitializer(fn: InitializerFn, options?: {
10
+ injector?: Injector;
11
+ }): void;
12
+ /**
13
+ * Registers an application destructor function that will be executed during application shutdown.
14
+ */
15
+ export declare function registerDestructor(fn: DestructorFn, options?: {
16
+ injector?: Injector;
17
+ }): void;
18
+ /**
19
+ * Registers a module or a function-based module in the application. If a class extending Module is provided, it will be registered directly. If a function is provided, it will be wrapped in a FunctionModule.
20
+ */
21
+ export declare function registerModule(fnOrModule: FunctionModuleFunction | Type<Module>, options?: {
22
+ injector?: Injector;
23
+ }): void;
24
+ export declare function provideModule(...functionsAndModules: OneOrMany<FunctionModuleFunction | Type<Module>>[]): ProvidersItem<Module | Type<Module>>[];
7
25
  export declare function provideInitializer(fn: InitializerFn): ProvidersItem;
8
26
  export declare function provideDestructor(fn: DestructorFn): ProvidersItem;
9
27
  export declare function provideShutdownSignal(signal: CancellationSignal): ProvidersItem;
@@ -1,5 +1,5 @@
1
1
  import { CancellationSignal } from '../cancellation/index.js';
2
- import { inject } from '../injector/index.js';
2
+ import { inject, Injector, provide } from '../injector/index.js';
3
3
  import { Logger } from '../logger/index.js';
4
4
  import { FunctionModule, Module } from '../module/index.js';
5
5
  import { isFunction } from '../utils/type-guards.js';
@@ -7,17 +7,44 @@ import { typeExtends } from '../utils/type/index.js';
7
7
  import { Application, APPLICATION_DESTRUCTOR, APPLICATION_INITIALIZER, APPLICATION_MODULE } from './application.js';
8
8
  const quitSignals = ['SIGTERM', 'SIGINT', 'SIGHUP', 'SIGBREAK'];
9
9
  const quitEvents = ['uncaughtException', 'unhandledRejection', 'rejectionHandled'];
10
+ /**
11
+ * Registers an application initializer function that will be executed during application startup.
12
+ */
13
+ export function registerInitializer(fn, options) {
14
+ const targetInjector = options?.injector ?? Injector;
15
+ targetInjector.register(APPLICATION_INITIALIZER, { useValue: fn }, { multi: true });
16
+ }
17
+ /**
18
+ * Registers an application destructor function that will be executed during application shutdown.
19
+ */
20
+ export function registerDestructor(fn, options) {
21
+ const targetInjector = options?.injector ?? Injector;
22
+ targetInjector.register(APPLICATION_DESTRUCTOR, { useValue: fn }, { multi: true });
23
+ }
24
+ /**
25
+ * Registers a module or a function-based module in the application. If a class extending Module is provided, it will be registered directly. If a function is provided, it will be wrapped in a FunctionModule.
26
+ */
27
+ export function registerModule(fnOrModule, options) {
28
+ const targetInjector = options?.injector ?? Injector;
29
+ if (typeExtends(fnOrModule, Module)) {
30
+ targetInjector.register(APPLICATION_MODULE, { useValue: fnOrModule }, { multi: true });
31
+ }
32
+ else if (isFunction(fnOrModule)) {
33
+ targetInjector.register(APPLICATION_MODULE, { useToken: FunctionModule, defaultArgument: fnOrModule }, { multi: true });
34
+ }
35
+ else {
36
+ throw new TypeError('Invalid module or function provided to registerModule');
37
+ }
38
+ }
10
39
  export function provideModule(...functionsAndModules) {
11
40
  return functionsAndModules.flat().map((fnOrModule) => {
12
41
  if (typeExtends(fnOrModule, Module)) {
13
- return { provide: APPLICATION_MODULE, useValue: fnOrModule, multi: true };
14
- }
15
- else if (isFunction(fnOrModule)) {
16
- return { provide: APPLICATION_MODULE, useToken: FunctionModule, defaultArgument: fnOrModule, multi: true };
42
+ return provide(APPLICATION_MODULE, { useValue: fnOrModule, multi: true });
17
43
  }
18
- else {
19
- throw new TypeError('Invalid module or function provided to provideModules');
44
+ if (isFunction(fnOrModule)) {
45
+ return provide(APPLICATION_MODULE, { useToken: FunctionModule, defaultArgument: fnOrModule, multi: true });
20
46
  }
47
+ throw new TypeError('Invalid module or function provided to provideModule');
21
48
  });
22
49
  }
23
50
  export function provideInitializer(fn) {
@@ -0,0 +1,267 @@
1
+ # Audit Module
2
+
3
+ The Audit module provides a robust, structured, and type-safe system for logging audit events to a database. It is designed to record detailed information about actions performed within an application, supporting hierarchical module structures, contextual data enrichment, and change tracking.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [✨ Features](#-features)
8
+ 2. [Core Concepts](#core-concepts)
9
+ - [The Auditor Service](#the-auditor-service)
10
+ - [Module Hierarchy](#module-hierarchy)
11
+ - [Contextual Enrichment](#contextual-enrichment)
12
+ - [The Audit Event Model](#the-audit-event-model)
13
+ 3. [🚀 Basic Usage](#-basic-usage)
14
+ - [Configuration](#configuration)
15
+ - [Logging Events](#logging-events)
16
+ 4. [🔧 Advanced Topics](#-advanced-topics)
17
+ - [Hierarchical Auditors with `fork()`](#hierarchical-auditors-with-fork)
18
+ - [Contextual Logging with `with()`](#contextual-logging-with-with)
19
+ - [Correlation IDs](#correlation-ids)
20
+ - [Type-Safe Event Payloads](#type-safe-event-payloads)
21
+ - [Logging Data Changes](#logging-data-changes)
22
+ 5. [📚 API](#-api)
23
+
24
+ ## ✨ Features
25
+
26
+ - **Structured Persistence**: Events are stored in a dedicated database table with a standardized schema (`AuditEvent`).
27
+ - **Modular Architecture**: Create hierarchical auditors (e.g., `user.auth.login`) to organize logs logically.
28
+ - **Context Propagation**: Enrich auditors with context (Tenant ID, User ID, Request Info) that applies to all subsequent logs.
29
+ - **Type Safety**: Define strict TypeScript interfaces for event payloads to ensure data consistency.
30
+ - **Change Tracking**: Built-in support for recording `before` and `after` states of modified data.
31
+ - **Correlation**: Trace related events across different services or modules using correlation IDs.
32
+ - **Severity Levels**: Categorize events by severity (`Info`, `Warn`, `Error`, `Critical`) with automatic outcome handling.
33
+
34
+ ## Core Concepts
35
+
36
+ ### The Auditor Service
37
+
38
+ The `Auditor` is the central injectable service. It is responsible for constructing the log entry and persisting it to the database.
39
+
40
+ **Key Characteristics:**
41
+ - **Immutability**: Methods like `fork()` or `with()` return new instances, allowing for safe concurrent usage in different contexts.
42
+ - **Dependency Injection**: Can be injected with a module name string, a module path array, or a configuration object.
43
+ - **Dual Logging**: Automatically writes to both the registered `Logger` and the database.
44
+
45
+ ```typescript
46
+ // Injection examples
47
+ const genericAuditor = inject(Auditor);
48
+ const moduleAuditor = inject(Auditor, 'products');
49
+ const pathAuditor = inject(Auditor, ['orders', 'shipping']);
50
+ const configAuditor = inject(Auditor, { module: 'users', context: { tenantId: '...' } });
51
+ ```
52
+
53
+ ### Module Hierarchy
54
+
55
+ Audit logs are organized by "modules". A module is a dot-separated string (e.g., `order-service.payment`). You can inject a root auditor and `fork` it to create sub-auditors, automatically appending to the module path.
56
+
57
+ ### Contextual Enrichment
58
+
59
+ An `Auditor` can carry context—such as the current actor, tenant, or network details. When you call `auditor.with(context)`, you get a new auditor where that context is merged into every log entry. This removes the need to pass repetitive metadata to every log call.
60
+
61
+ ### The Audit Event Model
62
+
63
+ Every log entry creates an `AuditEvent` record. This entity is stored in the `audit.event` table and captures:
64
+
65
+ - **Identity**: `id` (UUID), `timestamp`, `tenantId`.
66
+ - **Context**: `correlationId`, `module` (dot-separated path), `action`.
67
+ - **Outcome & Severity**: `outcome` (Success, Failure, etc.) and `severity` (Info, Warn, etc.).
68
+ - **Who**: `actorType`, `actor` (UUID), `impersonatorType`, `impersonator` (UUID).
69
+ - **Target**: `targetType`, `targetId`.
70
+ - **Environment**: `network` (embedded `RequestDetails` with IP, User Agent, Path, Session ID).
71
+ - **Payload**: `changes` (embedded `ChangeDetails` with before/after state) and `details` (action-specific JSON data).
72
+
73
+ ## 🚀 Basic Usage
74
+
75
+ ### Configuration
76
+
77
+ First, configure the audit module in your application's bootstrap phase. This sets up the database connection and runs necessary migrations.
78
+
79
+ ```typescript
80
+ import { configureAudit, migrateAuditSchema } from '@tstdl/base/audit';
81
+ import { runInInjectionContext } from '@tstdl/base/injector';
82
+ import { DatabaseConfig } from '@tstdl/base/orm/server';
83
+
84
+ // In your bootstrap function
85
+ export async function bootstrap() {
86
+ // Configure the module
87
+ configureAudit({
88
+ // Optional: Provide specific DB config if different from global
89
+ database: new DatabaseConfig({ ... })
90
+ });
91
+
92
+ // Run migrations to create the 'audit.event' table
93
+ await runInInjectionContext(injector, migrateAuditSchema);
94
+ }
95
+ ```
96
+
97
+ ### Logging Events
98
+
99
+ Inject the `Auditor` and use the helper methods (`info`, `warn`, `error`) to log events.
100
+
101
+ ```typescript
102
+ import { Auditor, ActorType } from '@tstdl/base/audit';
103
+ import { inject, Singleton } from '@tstdl/base/injector';
104
+
105
+ @Singleton()
106
+ export class ProductService {
107
+ // Inject an auditor specifically for the 'products' module
108
+ private readonly auditor = inject(Auditor, 'products');
109
+
110
+ async deleteProduct(productId: string, userId: string): Promise<void> {
111
+ // ... business logic ...
112
+
113
+ // Log the action
114
+ await this.auditor.info('delete', {
115
+ actor: userId,
116
+ actorType: ActorType.Subject,
117
+ targetId: productId,
118
+ targetType: 'Product',
119
+ details: {
120
+ reason: 'User requested deletion',
121
+ },
122
+ });
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## 🔧 Advanced Topics
128
+
129
+ ### Hierarchical Auditors with `fork()`
130
+
131
+ Use `fork()` to create specialized auditors for sub-features. This keeps your logs organized and traceable.
132
+
133
+ ```typescript
134
+ import { Auditor } from '@tstdl/base/audit';
135
+ import { inject } from '@tstdl/base/injector';
136
+
137
+ const userAuditor = inject(Auditor, 'user-management');
138
+
139
+ // Creates an auditor with module 'user-management.authentication'
140
+ const authAuditor = userAuditor.fork('authentication');
141
+
142
+ await authAuditor.info('login', {
143
+ /* ... */
144
+ });
145
+ ```
146
+
147
+ ### Contextual Logging with `with()`
148
+
149
+ In a request-handling context (like an API controller), create a scoped auditor that already knows _who_ is acting.
150
+
151
+ ```typescript
152
+ import { Auditor, ActorType } from '@tstdl/base/audit';
153
+ import { inject } from '@tstdl/base/injector';
154
+
155
+ async function handleRequest(request: Request, user: User) {
156
+ const baseAuditor = inject(Auditor);
157
+
158
+ // Create a context-aware auditor
159
+ const requestAuditor = baseAuditor.with({
160
+ tenantId: user.tenantId,
161
+ actor: user.id,
162
+ actorType: ActorType.Subject,
163
+ network: {
164
+ ipAddress: request.ip,
165
+ userAgent: request.headers['user-agent'],
166
+ path: request.url,
167
+ },
168
+ });
169
+
170
+ // Now logs are clean and automatically enriched
171
+ await requestAuditor.info('view-dashboard', {
172
+ targetId: 'dashboard-1',
173
+ targetType: 'Dashboard',
174
+ });
175
+ }
176
+ ```
177
+
178
+ ### Correlation IDs
179
+
180
+ To trace a single logical operation across multiple services or modules, use `withCorrelation()`. If a correlation ID exists in the context, it is preserved; otherwise, a new one is generated.
181
+
182
+ ```typescript
183
+ const traceAuditor = auditor.withCorrelation();
184
+
185
+ await traceAuditor.info('step-1');
186
+ // ... some operation ...
187
+ await traceAuditor.info('step-2');
188
+ // Both logs share the same correlationId
189
+ ```
190
+
191
+ ### Type-Safe Event Payloads
192
+
193
+ You can enforce the structure of the `details` object for specific actions by providing a type definition to the `Auditor`.
194
+
195
+ ```typescript
196
+ import { Auditor, ActorType } from '@tstdl/base/audit';
197
+
198
+ // Define the contract: Action Name -> Details Object
199
+ type UserEvents = {
200
+ create: { username: string; roles: string[] };
201
+ 'update-password': { method: 'email' | 'sms' };
202
+ };
203
+
204
+ class UserService {
205
+ // Apply the type to the auditor
206
+ private readonly auditor = inject(Auditor, 'users').fork<UserEvents>('management');
207
+
208
+ async createUser(username: string, roles: string[]) {
209
+ await this.auditor.info('create', {
210
+ actorType: ActorType.System,
211
+ targetId: 'new-uuid',
212
+ targetType: 'User',
213
+ details: {
214
+ username,
215
+ roles,
216
+ // TypeScript enforces that 'username' and 'roles' are present and correct
217
+ },
218
+ });
219
+ }
220
+ }
221
+ ```
222
+
223
+ ### Logging Data Changes
224
+
225
+ The `changes` property allows you to persist the state of an entity before and after a modification.
226
+
227
+ ```typescript
228
+ const oldProduct = await repo.load(id);
229
+ const newProduct = { ...oldProduct, price: 200 };
230
+
231
+ await auditor.info('update-price', {
232
+ targetId: id,
233
+ targetType: 'Product',
234
+ changes: {
235
+ before: oldProduct,
236
+ after: newProduct,
237
+ },
238
+ });
239
+ ```
240
+
241
+ ## 📚 API
242
+
243
+ | Export | Type | Description |
244
+ | :------------------- | :------- | :----------------------------------------------------------------- |
245
+ | `Auditor` | Class | The main service for logging. Can be typed with `Auditor<Events>`. |
246
+ | `AuditEvent` | Entity | The ORM entity representing a single audit log record. |
247
+ | `AuditModuleConfig` | Class | Configuration class for the audit module (database settings). |
248
+ | `configureAudit` | Function | Configures the audit module providers and settings. |
249
+ | `migrateAuditSchema` | Function | Runs database migrations for the audit schema. |
250
+ | `ActorType` | Enum | `Anonymous`, `System`, `ApiKey`, `Subject`. |
251
+ | `AuditOutcome` | Enum | `Pending`, `Success`, `Cancelled`, `Failure`, `Denied`. |
252
+ | `AuditSeverity` | Enum | `Info`, `Warn`, `Error`, `Critical`. |
253
+ | `RequestDetails` | Class | Model for embedded network details (IP, Agent, Path, Session). |
254
+ | `ChangeDetails` | Class | Model for embedded `before` and `after` data snapshots. |
255
+
256
+ ### Auditor Methods
257
+
258
+ | Method | Description |
259
+ | :----------------------- | :------------------------------------------------------------------------------- |
260
+ | `fork(subModule)` | Returns a new `Auditor` with the sub-module appended to the current module path. |
261
+ | `with(context)` | Returns a new `Auditor` with the provided context merged. |
262
+ | `withCorrelation()` | Returns a new `Auditor` with a correlation ID set (generates one if missing). |
263
+ | `log(action, data)` | Logs an event. Requires explicit `severity` and `outcome` in `data`. |
264
+ | `info(action, data)` | Logs with `Severity.Info` and defaults outcome to `Success`. |
265
+ | `warn(action, data)` | Logs with `Severity.Warn` and defaults outcome to `Failure`. |
266
+ | `error(action, data)` | Logs with `Severity.Error` and defaults outcome to `Failure`. |
267
+ | `critical(action, data)` | Logs with `Severity.Critical` and defaults outcome to `Failure`. |
package/audit/module.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare class AuditModuleConfig {
9
9
  * If not provided, the global database configuration is used.
10
10
  */
11
11
  database?: DatabaseConfig;
12
+ /**
13
+ * Whether to automatically run database migrations when the application starts.
14
+ * Defaults to `true`.
15
+ */
16
+ autoMigrate?: boolean;
12
17
  }
13
18
  /**
14
19
  * Configures audit server services.