@tstdl/base 0.93.139 → 0.93.140

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