@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/logger/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Logger
|
|
2
|
+
|
|
3
|
+
A flexible, extensible, and dependency-injection-friendly structured logging module for TypeScript applications. It features granular log level control, hierarchical context, and pluggable transports and formatters.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [Configuration](#configuration)
|
|
11
|
+
- [Injecting and Using the Logger](#injecting-and-using-the-logger)
|
|
12
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
13
|
+
- [Structured Context & Interpolation](#structured-context--interpolation)
|
|
14
|
+
- [Hierarchical Loggers (Forking)](#hierarchical-loggers-forking)
|
|
15
|
+
- [Managing Log Levels](#managing-log-levels)
|
|
16
|
+
- [Formatters](#formatters)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Dependency Injection First**: Designed to be configured once and injected anywhere using `@tstdl/base/injector`.
|
|
22
|
+
- **Structured Logging**: Attach arbitrary key-value context to logs for better observability.
|
|
23
|
+
- **Granular Level Control**: Set a global default level and override it for specific modules (e.g., `Database` vs `Http`).
|
|
24
|
+
- **Hierarchical Context**: Create child loggers (`fork`) that inherit or extend the parent's module path and context.
|
|
25
|
+
- **Message Interpolation**: Automatically inject context values into log messages using `{key}` syntax.
|
|
26
|
+
- **Pluggable Transports**: Comes with a `ConsoleLogTransport`, but easily extensible to file, network, or other outputs.
|
|
27
|
+
- **Formatters**: Includes `PrettyPrintLogFormatter` for development and `JsonLogFormatter` for production.
|
|
28
|
+
|
|
29
|
+
## Core Concepts
|
|
30
|
+
|
|
31
|
+
### Logger
|
|
32
|
+
|
|
33
|
+
The primary class used to emit logs. It is usually injected into services. A `Logger` instance holds a **module path** (e.g., `['Api', 'Users']`) and a **context** (key-value pairs).
|
|
34
|
+
|
|
35
|
+
### LogManager
|
|
36
|
+
|
|
37
|
+
A singleton service that orchestrates the logging system. It maintains the log level configuration (mapping module paths to levels) and dispatches log entries to all registered transports.
|
|
38
|
+
|
|
39
|
+
### LogLevel
|
|
40
|
+
|
|
41
|
+
An enumeration defining the severity of logs:
|
|
42
|
+
|
|
43
|
+
1. `Error` (0)
|
|
44
|
+
2. `Warn` (1)
|
|
45
|
+
3. `Info` (2)
|
|
46
|
+
4. `Verbose` (3)
|
|
47
|
+
5. `Debug` (4)
|
|
48
|
+
6. `Trace` (5)
|
|
49
|
+
|
|
50
|
+
### LogTransport
|
|
51
|
+
|
|
52
|
+
A destination for log entries. The module provides `ConsoleLogTransport`. Transports can have their own minimum log level (e.g., log everything to file, but only warnings to console).
|
|
53
|
+
|
|
54
|
+
### LogFormatter
|
|
55
|
+
|
|
56
|
+
Transforms a structured log payload into a string.
|
|
57
|
+
|
|
58
|
+
- **`PrettyPrintLogFormatter`**: Colorful, human-readable output (great for local dev).
|
|
59
|
+
- **`JsonLogFormatter`**: NDJSON (Newline Delimited JSON) output (standard for log aggregation systems).
|
|
60
|
+
|
|
61
|
+
## 🚀 Basic Usage
|
|
62
|
+
|
|
63
|
+
### Configuration
|
|
64
|
+
|
|
65
|
+
In your application's bootstrap or entry point, configure the `Injector` with the desired log level and transport.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { Injector } from '@tstdl/base/injector';
|
|
69
|
+
import { DEFAULT_LOG_LEVEL, LogLevel, provideConsoleLogTransport, PrettyPrintLogFormatter } from '@tstdl/base/logger';
|
|
70
|
+
|
|
71
|
+
// 1. Set the global default log level (optional, defaults to Trace)
|
|
72
|
+
Injector.register(DEFAULT_LOG_LEVEL, { useValue: LogLevel.Info });
|
|
73
|
+
|
|
74
|
+
// 2. Register the Console Transport
|
|
75
|
+
// Use PrettyPrintLogFormatter for development
|
|
76
|
+
Injector.register(provideConsoleLogTransport(PrettyPrintLogFormatter));
|
|
77
|
+
|
|
78
|
+
// Alternatively, for production JSON logging:
|
|
79
|
+
// import { JsonLogFormatter } from '@tstdl/base/logger';
|
|
80
|
+
// Injector.register(provideConsoleLogTransport(JsonLogFormatter));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Injecting and Using the Logger
|
|
84
|
+
|
|
85
|
+
Inject the `Logger` into your services. You can pass a string or array of strings to the `inject` function to define the **module name**.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Injectable, inject } from '@tstdl/base/injector';
|
|
89
|
+
import { Logger } from '@tstdl/base/logger';
|
|
90
|
+
|
|
91
|
+
@Injectable()
|
|
92
|
+
export class UserService {
|
|
93
|
+
// Inject logger and tag it with 'UserService' module
|
|
94
|
+
private readonly logger = inject(Logger, 'UserService');
|
|
95
|
+
|
|
96
|
+
async getUser(id: string): Promise<void> {
|
|
97
|
+
this.logger.info(`Fetching user ${id}`);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// ... logic ...
|
|
101
|
+
this.logger.debug('User fetched successfully');
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Overload 1: Pass only the error and context
|
|
104
|
+
this.logger.error(error, { userId: id });
|
|
105
|
+
|
|
106
|
+
// Overload 2: Pass a custom message, the error, and context
|
|
107
|
+
this.logger.error('Failed to fetch user', error, { userId: id });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 🔧 Advanced Topics
|
|
114
|
+
|
|
115
|
+
### Structured Context & Interpolation
|
|
116
|
+
|
|
117
|
+
You can pass a context object as the second argument to any log method. Values in the context can be interpolated into the message using `{key}` syntax. It also supports nested property access using dot notation.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const user = { id: 'u-123', profile: { name: 'John Doe' } };
|
|
121
|
+
const duration = 45;
|
|
122
|
+
|
|
123
|
+
// Context is attached to the log entry
|
|
124
|
+
this.logger.info('Request processed', { user, duration });
|
|
125
|
+
|
|
126
|
+
// Interpolation: The message will become "User John Doe (u-123) processed in 45ms"
|
|
127
|
+
this.logger.info('User {user.profile.name} ({user.id}) processed in {duration}ms', { user, duration });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
You can also create a temporary logger with bound context using `.with()`:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const requestLogger = this.logger.with({ requestId: 'req-abc' });
|
|
134
|
+
|
|
135
|
+
requestLogger.info('Step 1'); // Context: { requestId: 'req-abc' }
|
|
136
|
+
requestLogger.info('Step 2'); // Context: { requestId: 'req-abc' }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Hierarchical Loggers (Forking)
|
|
140
|
+
|
|
141
|
+
Use `.fork()` to create a sub-logger. This appends to the module path and inherits the parent's context. This is useful for passing loggers down to sub-components while maintaining the overall context.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Parent logger module: ['Database']
|
|
145
|
+
const dbLogger = inject(Logger, 'Database');
|
|
146
|
+
|
|
147
|
+
// Child logger module: ['Database', 'Connection']
|
|
148
|
+
const connLogger = dbLogger.fork('Connection');
|
|
149
|
+
|
|
150
|
+
connLogger.info('Connected');
|
|
151
|
+
// Output: ... INFO [Database Connection] Connected
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You can also replace or extend context during a fork, or even specify a fixed log level for the new logger:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const taskLogger = this.logger.fork('TaskWorker', {
|
|
158
|
+
context: { taskId: 1 },
|
|
159
|
+
replaceContext: false, // merge with parent context (default)
|
|
160
|
+
level: LogLevel.Debug // Force this logger to Debug level, ignoring LogManager rules
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Using `.with()` is similar to `.fork()`, but it only adds context without changing the module path.
|
|
165
|
+
|
|
166
|
+
### Advanced Logger Injection
|
|
167
|
+
|
|
168
|
+
When injecting a `Logger`, you can provide more than just a module name. The injection argument supports an options object:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
@Injectable()
|
|
172
|
+
export class DebugService {
|
|
173
|
+
// Inject with fixed level and initial context
|
|
174
|
+
private readonly logger = inject(Logger, {
|
|
175
|
+
module: 'DebugService',
|
|
176
|
+
level: LogLevel.Trace,
|
|
177
|
+
context: { environment: 'development' }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Managing Log Levels
|
|
183
|
+
|
|
184
|
+
The `LogManager` allows you to set log levels dynamically for specific modules. This is useful for debugging specific parts of an application without enabling debug logs globally.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { inject } from '@tstdl/base/injector';
|
|
188
|
+
import { LogManager, LogLevel } from '@tstdl/base/logger';
|
|
189
|
+
|
|
190
|
+
const logManager = inject(LogManager);
|
|
191
|
+
|
|
192
|
+
// Set default level for everything
|
|
193
|
+
logManager.setDefaultLevel(LogLevel.Info);
|
|
194
|
+
|
|
195
|
+
// Enable Debug logs for the 'Database' module and all its sub-modules
|
|
196
|
+
logManager.setModuleLevel('Database', LogLevel.Debug);
|
|
197
|
+
|
|
198
|
+
// Enable Trace logs specifically for 'Database.Connection'
|
|
199
|
+
logManager.setModuleLevel(['Database', 'Connection'], LogLevel.Trace);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Formatters
|
|
203
|
+
|
|
204
|
+
Formatters determine how the log payload is converted to a string.
|
|
205
|
+
|
|
206
|
+
**PrettyPrintLogFormatter**
|
|
207
|
+
Outputs colorful, human-readable logs.
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
2023-10-27T10:00:00.000Z INFO: [UserService] User created {"id":1}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**JsonLogFormatter**
|
|
214
|
+
Outputs a JSON string for each log line. All context properties are spread into the top-level JSON object.
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{ "timestamp": "2023-10-27T10:00:00.000Z", "level": "info", "module": "UserService", "message": "User created", "id": 1 }
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Custom Transports
|
|
221
|
+
|
|
222
|
+
To create a custom transport, extend the `LogTransport` class and implement the `log` method.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { Singleton } from '@tstdl/base/injector';
|
|
226
|
+
import { LogTransport, LogPayload, provideLogTransport, LogLevel } from '@tstdl/base/logger';
|
|
227
|
+
|
|
228
|
+
@Singleton()
|
|
229
|
+
export class MyCustomTransport extends LogTransport {
|
|
230
|
+
override log(payload: LogPayload): void {
|
|
231
|
+
// Implement custom logic (e.g., send to an external API)
|
|
232
|
+
console.log('Custom transport:', payload.message);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Register it in the Injector
|
|
237
|
+
Injector.register(provideLogTransport({ useToken: MyCustomTransport }));
|
|
238
|
+
|
|
239
|
+
// You can also specify a minimum level for the transport
|
|
240
|
+
Injector.register(provideLogTransport({ useToken: MyCustomTransport, defaultArgument: { level: LogLevel.Error } }));
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Multiple Transports
|
|
244
|
+
|
|
245
|
+
You can register as many transports as you need. Each log entry will be dispatched to all registered transports whose minimum log level is satisfied.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Log everything to console in pretty format
|
|
249
|
+
Injector.register(provideConsoleLogTransport(PrettyPrintLogFormatter));
|
|
250
|
+
|
|
251
|
+
// ALSO log warnings and errors to a custom transport
|
|
252
|
+
Injector.register(provideLogTransport({
|
|
253
|
+
useToken: MyCustomTransport,
|
|
254
|
+
defaultArgument: { level: LogLevel.Warn }
|
|
255
|
+
}));
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 📚 API
|
|
259
|
+
|
|
260
|
+
### Classes
|
|
261
|
+
|
|
262
|
+
| Class | Description |
|
|
263
|
+
| :------------------------ | :--------------------------------------------------------------------------------------------------------------------- |
|
|
264
|
+
| `Logger` | The main logging interface. Supports methods `error`, `warn`, `info`, `verbose`, `debug`, `trace`, `fork`, and `with`. |
|
|
265
|
+
| `LogManager` | Singleton that manages log levels and dispatches logs to transports. |
|
|
266
|
+
| `LogTransport` | Abstract base class for log destinations. |
|
|
267
|
+
| `ConsoleLogTransport` | A transport that writes to `console.log`, `console.error`, etc. |
|
|
268
|
+
| `LogFormatter` | Abstract base class for formatting log payloads. |
|
|
269
|
+
| `JsonLogFormatter` | Formats logs as JSON strings. |
|
|
270
|
+
| `PrettyPrintLogFormatter` | Formats logs for human readability with colors. |
|
|
271
|
+
|
|
272
|
+
### Enums & Types
|
|
273
|
+
|
|
274
|
+
| Name | Description |
|
|
275
|
+
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------- |
|
|
276
|
+
| `LogLevel` | Enum: `Error` (0), `Warn` (1), `Info` (2), `Verbose` (3), `Debug` (4), `Trace` (5). |
|
|
277
|
+
| `LogPayload` | The structured data object passed to formatters and transports. Contains `timestamp`, `level`, `module`, `message`, and `context`. |
|
|
278
|
+
| `LoggerArgument` | Type for the argument passed to `inject(Logger, argument)`. Can be a string (module name), string array, or options object. |
|
|
279
|
+
| `LoggerForkOptions` | Options for creating a child logger via `.fork()`. |
|
|
280
|
+
|
|
281
|
+
### Functions & Tokens
|
|
282
|
+
|
|
283
|
+
| Name | Description |
|
|
284
|
+
| :---------------------------------------------- | :------------------------------------------------------------ |
|
|
285
|
+
| `provideLogTransport(provider)` | Helper to register any `LogTransport` with `multi: true`. |
|
|
286
|
+
| `provideConsoleLogTransport(formatter, level?)` | Helper specifically for registering the `ConsoleLogTransport`. |
|
|
287
|
+
| `DEFAULT_LOG_LEVEL` | Injection token to configure the default global log level. |
|
package/mail/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Mail
|
|
2
|
+
|
|
3
|
+
A robust module for sending emails with powerful templating capabilities, pluggable mail clients, and automatic database logging.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [Configuration](#configuration)
|
|
11
|
+
- [Sending a Simple Email](#sending-a-simple-email)
|
|
12
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
13
|
+
- [Using Templates](#using-templates)
|
|
14
|
+
- [Per-Request Client Configuration](#per-request-client-configuration)
|
|
15
|
+
- [Implementing a Custom Mail Client](#implementing-a-custom-mail-client)
|
|
16
|
+
- [📚 API](#-api)
|
|
17
|
+
|
|
18
|
+
## ✨ Features
|
|
19
|
+
|
|
20
|
+
- **Pluggable Mail Clients**: Abstracted `MailClient` allows for different sending backends. A `NodemailerMailClient` is provided out-of-the-box.
|
|
21
|
+
- **Powerful Templating**: Integrates with `@tstdl/base/templates` to generate email subjects, HTML, and text bodies from templates and context data.
|
|
22
|
+
- **Automatic Logging**: Every mail-sending attempt is logged to the database (`mail.log` table), including data, results, and errors, for auditing and debugging.
|
|
23
|
+
- **Flexible Configuration**: Easily configure default clients, connection settings (host, port, auth), and default mail data (e.g., a global `from` address).
|
|
24
|
+
- **Type Safety**: Fully typed API for mail data, templates, and configuration.
|
|
25
|
+
|
|
26
|
+
## Core Concepts
|
|
27
|
+
|
|
28
|
+
### MailService
|
|
29
|
+
|
|
30
|
+
The `MailService` is the central component. It orchestrates the rendering of templates (if used), delegates the actual sending to the configured `MailClient`, and ensures the transaction is logged to the database.
|
|
31
|
+
|
|
32
|
+
### MailClient
|
|
33
|
+
|
|
34
|
+
`MailClient` is an abstract class defining the contract for sending emails. The module includes `NodemailerMailClient`, which uses `nodemailer` under the hood. You can implement your own client (e.g., for SendGrid, AWS SES API) by extending this class.
|
|
35
|
+
|
|
36
|
+
### MailTemplate
|
|
37
|
+
|
|
38
|
+
A `MailTemplate` defines the structure of an email (subject, html, text) using the `@tstdl/base/templates` system. This allows for dynamic content generation based on a context object.
|
|
39
|
+
|
|
40
|
+
## 🚀 Basic Usage
|
|
41
|
+
|
|
42
|
+
### Configuration
|
|
43
|
+
|
|
44
|
+
To use the mail module, you must configure it during your application's bootstrap phase. This involves registering the configuration and running migrations to create the necessary database tables.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { configureMail, NodemailerMailClient, migrateMailSchema } from '@tstdl/base/mail';
|
|
48
|
+
import { configureOrm } from '@tstdl/base/orm/server';
|
|
49
|
+
import { inject, Injector } from '@tstdl/base/injector';
|
|
50
|
+
|
|
51
|
+
async function bootstrap() {
|
|
52
|
+
// Ensure ORM is configured first as MailService relies on it for logging
|
|
53
|
+
configureOrm({
|
|
54
|
+
connection: {
|
|
55
|
+
host: 'localhost',
|
|
56
|
+
port: 5432,
|
|
57
|
+
user: 'db_user',
|
|
58
|
+
password: 'db_password',
|
|
59
|
+
database: 'my_app',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Configure the Mail Module
|
|
64
|
+
configureMail({
|
|
65
|
+
// Use the default database connection configured above
|
|
66
|
+
// You can also pass a specific DatabaseConfig object here
|
|
67
|
+
database: undefined,
|
|
68
|
+
|
|
69
|
+
// Specify the client implementation to use
|
|
70
|
+
client: NodemailerMailClient,
|
|
71
|
+
|
|
72
|
+
// Default SMTP settings
|
|
73
|
+
defaultClientConfig: {
|
|
74
|
+
host: 'smtp.example.com',
|
|
75
|
+
port: 587,
|
|
76
|
+
secure: false, // true for 465, false for other ports
|
|
77
|
+
auth: {
|
|
78
|
+
user: 'user@example.com',
|
|
79
|
+
password: 'secret-password',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Default data applied to every email (e.g., global sender)
|
|
84
|
+
defaultData: {
|
|
85
|
+
from: { name: 'My App Support', address: 'support@myapp.com' },
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Run migrations to create the 'mail.log' table
|
|
90
|
+
const injector = inject(Injector);
|
|
91
|
+
await injector.run(migrateMailSchema);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Sending a Simple Email
|
|
96
|
+
|
|
97
|
+
Inject `MailService` and use the `send` method to dispatch an email.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { Singleton, inject } from '@tstdl/base/injector';
|
|
101
|
+
import { MailService } from '@tstdl/base/mail';
|
|
102
|
+
|
|
103
|
+
@Singleton()
|
|
104
|
+
export class NotificationService {
|
|
105
|
+
readonly #mailService = inject(MailService);
|
|
106
|
+
|
|
107
|
+
async sendWelcome(email: string, name: string): Promise<void> {
|
|
108
|
+
await this.#mailService.send({
|
|
109
|
+
to: { name, address: email },
|
|
110
|
+
subject: 'Welcome to My App',
|
|
111
|
+
content: {
|
|
112
|
+
text: `Hello ${name}, welcome to our platform!`,
|
|
113
|
+
html: `<p>Hello <strong>${name}</strong>, welcome to our platform!</p>`,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🔧 Advanced Topics
|
|
121
|
+
|
|
122
|
+
### Using Templates
|
|
123
|
+
|
|
124
|
+
You can define reusable templates for your emails. This requires the `@tstdl/base/templates` module to be configured.
|
|
125
|
+
|
|
126
|
+
**1. Define the Template**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { mailTemplate } from '@tstdl/base/mail';
|
|
130
|
+
import { stringTemplateField } from '@tstdl/base/templates/resolvers';
|
|
131
|
+
|
|
132
|
+
// Define the context type for type safety
|
|
133
|
+
type PasswordResetContext = {
|
|
134
|
+
name: string;
|
|
135
|
+
resetLink: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const passwordResetTemplate = mailTemplate<PasswordResetContext>('password-reset', {
|
|
139
|
+
subject: stringTemplateField({
|
|
140
|
+
renderer: 'string',
|
|
141
|
+
template: 'Password Reset Request for {{ name }}',
|
|
142
|
+
}),
|
|
143
|
+
text: stringTemplateField({
|
|
144
|
+
renderer: 'string',
|
|
145
|
+
template: 'Hi {{ name }},\n\nPlease click here to reset your password: {{ resetLink }}',
|
|
146
|
+
}),
|
|
147
|
+
// You can also add an 'html' field here using jsxTemplateField or others
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**2. Send using the Template**
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { Singleton, inject } from '@tstdl/base/injector';
|
|
155
|
+
import { MailService } from '@tstdl/base/mail';
|
|
156
|
+
import { passwordResetTemplate } from './mail-templates.js';
|
|
157
|
+
|
|
158
|
+
@Singleton()
|
|
159
|
+
export class AuthService {
|
|
160
|
+
readonly #mailService = inject(MailService);
|
|
161
|
+
|
|
162
|
+
async requestPasswordReset(email: string, name: string): Promise<void> {
|
|
163
|
+
const context = {
|
|
164
|
+
name,
|
|
165
|
+
resetLink: 'https://myapp.com/reset?token=12345',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// sendTemplate automatically renders subject, text, and html
|
|
169
|
+
await this.#mailService.sendTemplate(
|
|
170
|
+
passwordResetTemplate,
|
|
171
|
+
{ to: email }, // MailData (excluding content and subject)
|
|
172
|
+
context,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Per-Request Client Configuration
|
|
179
|
+
|
|
180
|
+
Sometimes you need to send specific emails using a different SMTP server or credentials (e.g., transactional vs. marketing emails). You can override the default configuration per call.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { Singleton, inject } from '@tstdl/base/injector';
|
|
184
|
+
import { MailService, type MailClientConfig } from '@tstdl/base/mail';
|
|
185
|
+
|
|
186
|
+
@Singleton()
|
|
187
|
+
export class MarketingService {
|
|
188
|
+
readonly #mailService = inject(MailService);
|
|
189
|
+
|
|
190
|
+
async sendNewsletter(email: string): Promise<void> {
|
|
191
|
+
const marketingConfig: MailClientConfig = {
|
|
192
|
+
host: 'smtp.marketing-provider.com',
|
|
193
|
+
port: 587,
|
|
194
|
+
auth: {
|
|
195
|
+
user: 'marketing',
|
|
196
|
+
password: 'marketing-password',
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
await this.#mailService.send(
|
|
201
|
+
{
|
|
202
|
+
to: email,
|
|
203
|
+
subject: 'Weekly Newsletter',
|
|
204
|
+
content: { text: '...' },
|
|
205
|
+
},
|
|
206
|
+
marketingConfig, // Override config
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Implementing a Custom Mail Client
|
|
213
|
+
|
|
214
|
+
To support a different provider (e.g., SendGrid API), extend `MailClient` and register it.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { Singleton } from '@tstdl/base/injector';
|
|
218
|
+
import { MailClient, type MailData, type MailSendResult, type MailClientConfig } from '@tstdl/base/mail';
|
|
219
|
+
|
|
220
|
+
@Singleton()
|
|
221
|
+
export class MyCustomMailClient extends MailClient {
|
|
222
|
+
async send(data: MailData, clientConfig?: MailClientConfig): Promise<MailSendResult> {
|
|
223
|
+
// Implement sending logic here (e.g., fetch call to API)
|
|
224
|
+
console.log('Sending mail via custom client', data);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
messageId: 'custom-id-123',
|
|
228
|
+
accepted: [data.to as any],
|
|
229
|
+
rejected: [],
|
|
230
|
+
pending: [],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// In bootstrap:
|
|
236
|
+
configureMail({
|
|
237
|
+
client: MyCustomMailClient,
|
|
238
|
+
// ...
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## 📚 API
|
|
243
|
+
|
|
244
|
+
| Export | Type | Description |
|
|
245
|
+
| :--------------------- | :--------------- | :----------------------------------------------------------- |
|
|
246
|
+
| `MailService` | `class` | Main service for sending emails and logging. |
|
|
247
|
+
| `MailClient` | `abstract class` | Base class for mail sending implementations. |
|
|
248
|
+
| `NodemailerMailClient` | `class` | Implementation of `MailClient` using Nodemailer. |
|
|
249
|
+
| `configureMail` | `function` | Configures the module (database, client, defaults). |
|
|
250
|
+
| `migrateMailSchema` | `function` | Runs database migrations for the `mail` schema. |
|
|
251
|
+
| `mailTemplate` | `function` | Helper to define a type-safe `MailTemplate`. |
|
|
252
|
+
| `MailData` | `type` | Structure for email data (to, from, subject, content, etc.). |
|
|
253
|
+
| `MailSendResult` | `type` | Result returned after sending an email. |
|
|
254
|
+
| `MailClientConfig` | `class` | Configuration object for mail clients (host, port, auth). |
|
|
255
|
+
| `MailLog` | `class` | Entity representing the database log entry for sent mails. |
|
|
256
|
+
| `MAIL_DEFAULT_DATA` | `InjectionToken` | Token for injecting default mail data. |
|
package/memory/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @tstdl/base/memory
|
|
2
|
+
|
|
3
|
+
Utilities for advanced memory management and garbage collection observation in TypeScript. This module provides enhanced wrappers around the native `FinalizationRegistry` API, enabling multiple cleanup handlers per object and reactive garbage collection events via RxJS.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
11
|
+
- [Multiple Handlers](#multiple-handlers)
|
|
12
|
+
- [Observable Finalization Registry](#observable-finalization-registry)
|
|
13
|
+
- [Unregistering Handlers](#unregistering-handlers)
|
|
14
|
+
- [📚 API](#-api)
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
- **Multiple Finalization Handlers**: Attach multiple independent cleanup callbacks to a single object instance without conflicts.
|
|
19
|
+
- **Reactive GC Events**: Use `ObservableFinalizationRegistry` to receive RxJS notifications when objects are garbage collected.
|
|
20
|
+
- **Type-Safe Context**: Pass strongly-typed data to your finalization handlers to aid in cleanup (e.g., closing handles, logging IDs).
|
|
21
|
+
- **Abstraction**: Hides the complexity of managing `FinalizationRegistry` instances and tokens.
|
|
22
|
+
|
|
23
|
+
## Core Concepts
|
|
24
|
+
|
|
25
|
+
JavaScript's `FinalizationRegistry` allows you to request a callback when an object is garbage collected. However, managing these registries can be cumbersome, especially if different parts of your application need to attach different cleanup logic to the same object.
|
|
26
|
+
|
|
27
|
+
The **Memory** module solves this by:
|
|
28
|
+
|
|
29
|
+
1. **Centralizing Registry Management**: `registerFinalization` uses a shared internal registry and a mapping system to allow multiple handlers for one object.
|
|
30
|
+
2. **Bridging to RxJS**: `ObservableFinalizationRegistry` turns the callback-based API into a stream-based API, making it easier to integrate with reactive architectures.
|
|
31
|
+
|
|
32
|
+
> **Note**: Garbage collection is non-deterministic. There is no guarantee _when_ or _if_ the cleanup handlers will run. These tools should be used for auxiliary cleanup (like releasing external resources or debugging leaks), not for critical program logic.
|
|
33
|
+
|
|
34
|
+
## 🚀 Basic Usage
|
|
35
|
+
|
|
36
|
+
The most common use case is attaching a cleanup function to an object. This function will run after the object has been garbage collected.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { registerFinalization } from '@tstdl/base/memory';
|
|
40
|
+
|
|
41
|
+
class Resource {
|
|
42
|
+
id: number;
|
|
43
|
+
|
|
44
|
+
constructor(id: number) {
|
|
45
|
+
this.id = id;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 1. Create an object
|
|
50
|
+
let myResource: Resource | null = new Resource(123);
|
|
51
|
+
|
|
52
|
+
// 2. Register a cleanup handler
|
|
53
|
+
// We pass the ID as data because we can't access 'myResource' inside the handler
|
|
54
|
+
// (it will be gone by then).
|
|
55
|
+
registerFinalization(
|
|
56
|
+
myResource,
|
|
57
|
+
(id) => {
|
|
58
|
+
console.log(`Resource with ID ${id} was garbage collected.`);
|
|
59
|
+
},
|
|
60
|
+
123,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 3. Dereference the object to make it eligible for GC
|
|
64
|
+
myResource = null;
|
|
65
|
+
|
|
66
|
+
// ... Sometime later, when GC runs:
|
|
67
|
+
// Output: "Resource with ID 123 was garbage collected."
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Warning**: Never pass the object being registered as the `data` payload, nor any object that holds a strong reference to it. Doing so will create a strong reference cycle and prevent the object from ever being garbage collected.
|
|
71
|
+
|
|
72
|
+
## 🔧 Advanced Topics
|
|
73
|
+
|
|
74
|
+
### Multiple Handlers
|
|
75
|
+
|
|
76
|
+
Unlike the native `FinalizationRegistry`, which only allows one "held value" per registration, `registerFinalization` allows you to attach any number of handlers to the same object.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { registerFinalization } from '@tstdl/base/memory';
|
|
80
|
+
|
|
81
|
+
const obj = { name: 'MultiHandler' };
|
|
82
|
+
|
|
83
|
+
registerFinalization(obj, () => console.log('Handler 1: Cleanup successful.'));
|
|
84
|
+
registerFinalization(obj, () => console.log('Handler 2: Notifying external system.'));
|
|
85
|
+
|
|
86
|
+
// Both handlers will run independently when 'obj' is collected.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Observable Finalization Registry
|
|
90
|
+
|
|
91
|
+
If you prefer working with RxJS Observables, `ObservableFinalizationRegistry` emits values to a stream whenever a registered object is reclaimed.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { ObservableFinalizationRegistry } from '@tstdl/base/memory';
|
|
95
|
+
|
|
96
|
+
// Create a registry that expects a string token
|
|
97
|
+
const registry = new ObservableFinalizationRegistry<string>();
|
|
98
|
+
|
|
99
|
+
// Subscribe to the stream
|
|
100
|
+
const subscription = registry.finalize$.subscribe((token) => {
|
|
101
|
+
console.log(`Object associated with token "${token}" was collected.`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Register an object
|
|
105
|
+
let tempObject: object | null = {};
|
|
106
|
+
registry.register(tempObject, 'unique-token-abc');
|
|
107
|
+
|
|
108
|
+
// Dereference
|
|
109
|
+
tempObject = null;
|
|
110
|
+
|
|
111
|
+
// ... Sometime later:
|
|
112
|
+
// Output: "Object associated with token "unique-token-abc" was collected."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Unregistering Handlers
|
|
116
|
+
|
|
117
|
+
You can remove a specific cleanup handler if it is no longer needed (e.g., if the resource was manually closed).
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { registerFinalization, unregisterFinalization } from '@tstdl/base/memory';
|
|
121
|
+
|
|
122
|
+
const data = { value: 'important' };
|
|
123
|
+
|
|
124
|
+
const cleanup = () => {
|
|
125
|
+
console.log('Cleanup data');
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Register
|
|
129
|
+
registerFinalization(data, cleanup);
|
|
130
|
+
|
|
131
|
+
// Unregister specifically this handler
|
|
132
|
+
unregisterFinalization(data, cleanup);
|
|
133
|
+
|
|
134
|
+
// Even if 'data' is garbage collected now, 'cleanup' will NOT run.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 📚 API
|
|
138
|
+
|
|
139
|
+
| Export | Type | Description |
|
|
140
|
+
| :------------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `registerFinalization` | Function | Registers a callback to be invoked after the target object is garbage collected. Supports multiple handlers and a data payload. |
|
|
142
|
+
| `unregisterFinalization` | Function | Unregisters a specific callback for a target object without affecting other registered handlers for the same object. |
|
|
143
|
+
| `ObservableFinalizationRegistry` | Class | Extends native `FinalizationRegistry`. Exposes a `finalize$` Observable that emits the `heldValue` when an object is collected. |
|
|
144
|
+
| `FinalizationHandler<D>` | Type | Type definition for the callback function: `(data: D) => any`. `D` defaults to `any` and is inferred when using `registerFinalization`. |
|