@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
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
|
-
*
|
|
136
|
+
* CancellationSignal to cancel the request.
|
|
136
137
|
*/
|
|
137
|
-
|
|
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. |
|
|
@@ -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 })
|
|
30
|
+
#shutdownToken = new CancellationToken(inject(CancellationSignal, undefined, { optional: true }));
|
|
31
31
|
get injector() {
|
|
32
32
|
return this.#injector;
|
|
33
33
|
}
|
|
@@ -84,7 +84,7 @@ let Application = Application_1 = class Application {
|
|
|
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) {
|
package/audit/README.md
ADDED
|
@@ -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`. |
|