@tstdl/base 0.93.139 → 0.93.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
@@ -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/mail/module.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Injector } from '../injector/injector.js';
1
2
  import { type DatabaseConfig } from '../orm/server/index.js';
2
3
  import type { Type } from '../types/index.js';
3
4
  import { MailClient, MailClientConfig } from './mail.client.js';
@@ -7,9 +8,12 @@ export declare class MailModuleConfig {
7
8
  defaultClientConfig?: MailClientConfig;
8
9
  client?: Type<MailClient>;
9
10
  defaultData?: DefaultMailData;
11
+ autoMigrate?: boolean;
10
12
  }
11
13
  /**
12
14
  * configure mail module
13
15
  */
14
- export declare function configureMail(config: MailModuleConfig): void;
16
+ export declare function configureMail({ injector, ...config }: MailModuleConfig & {
17
+ injector?: Injector;
18
+ }): void;
15
19
  export declare function migrateMailSchema(): Promise<void>;
package/mail/module.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { inject } from '../injector/index.js';
2
2
  import { Injector } from '../injector/injector.js';
3
- import { Database, migrate } from '../orm/server/index.js';
3
+ import { Database, migrate, registerDatabaseMigration } from '../orm/server/index.js';
4
4
  import { isDefined } from '../utils/type-guards.js';
5
5
  import { MailClient, MailClientConfig } from './mail.client.js';
6
6
  import { MAIL_DEFAULT_DATA } from './tokens.js';
@@ -9,20 +9,25 @@ export class MailModuleConfig {
9
9
  defaultClientConfig;
10
10
  client;
11
11
  defaultData;
12
+ autoMigrate;
12
13
  }
13
14
  /**
14
15
  * configure mail module
15
16
  */
16
- export function configureMail(config) {
17
- Injector.register(MailModuleConfig, { useValue: config });
17
+ export function configureMail({ injector, ...config }) {
18
+ const targetInjector = injector ?? Injector;
19
+ targetInjector.register(MailModuleConfig, { useValue: config });
18
20
  if (isDefined(config.defaultClientConfig)) {
19
- Injector.registerSingleton(MailClientConfig, { useValue: config.defaultClientConfig });
21
+ targetInjector.registerSingleton(MailClientConfig, { useValue: config.defaultClientConfig });
20
22
  }
21
23
  if (isDefined(config.client)) {
22
- Injector.registerSingleton(MailClient, { useToken: config.client });
24
+ targetInjector.registerSingleton(MailClient, { useToken: config.client });
23
25
  }
24
26
  if (isDefined(config.defaultData)) {
25
- Injector.registerSingleton(MAIL_DEFAULT_DATA, { useValue: config.defaultData });
27
+ targetInjector.registerSingleton(MAIL_DEFAULT_DATA, { useValue: config.defaultData });
28
+ }
29
+ if (config.autoMigrate != false) {
30
+ registerDatabaseMigration('Mail', migrateMailSchema, { injector });
26
31
  }
27
32
  }
28
33
  export async function migrateMailSchema() {