@jmlq/logger-plugin-fs 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (118) hide show
  1. package/README.md +229 -0
  2. package/architecture.md +426 -0
  3. package/dist/application/dto/index.d.ts +1 -0
  4. package/dist/application/dto/index.js +17 -0
  5. package/dist/application/dto/rotate-if-needed.request.d.ts +10 -0
  6. package/dist/application/dto/rotate-if-needed.request.js +2 -0
  7. package/dist/application/factory/create-fs-datasource.factory.d.ts +16 -0
  8. package/dist/application/factory/create-fs-datasource.factory.js +51 -0
  9. package/dist/application/factory/index.d.ts +1 -0
  10. package/dist/application/factory/index.js +17 -0
  11. package/dist/application/use-cases/append-log.use-case.d.ts +30 -0
  12. package/dist/application/use-cases/append-log.use-case.js +39 -0
  13. package/dist/application/use-cases/ensure-directory.use-case.d.ts +58 -0
  14. package/dist/application/use-cases/ensure-directory.use-case.js +64 -0
  15. package/dist/application/use-cases/find-logs-use-case.d.ts +17 -0
  16. package/dist/application/use-cases/find-logs-use-case.js +64 -0
  17. package/dist/application/use-cases/index.d.ts +5 -0
  18. package/dist/application/use-cases/index.js +21 -0
  19. package/dist/application/use-cases/persist-log.use-case.d.ts +96 -0
  20. package/dist/application/use-cases/persist-log.use-case.js +105 -0
  21. package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +55 -0
  22. package/dist/application/use-cases/rotate-if-needed.use-case.js +61 -0
  23. package/dist/domain/model/index.d.ts +1 -0
  24. package/dist/domain/model/index.js +17 -0
  25. package/dist/domain/model/log-entry.model.d.ts +8 -0
  26. package/dist/domain/model/log-entry.model.js +2 -0
  27. package/dist/domain/ports/file/file-path.port.d.ts +38 -0
  28. package/dist/domain/ports/file/file-path.port.js +2 -0
  29. package/dist/domain/ports/file/file-rotator.port.d.ts +42 -0
  30. package/dist/domain/ports/file/file-rotator.port.js +2 -0
  31. package/dist/domain/ports/file/index.d.ts +2 -0
  32. package/dist/domain/ports/file/index.js +18 -0
  33. package/dist/domain/ports/file/log-stream-writer.port.d.ts +70 -0
  34. package/dist/domain/ports/file/log-stream-writer.port.js +2 -0
  35. package/dist/domain/ports/filesystem-provider.port.d.ts +61 -0
  36. package/dist/domain/ports/filesystem-provider.port.js +2 -0
  37. package/dist/domain/ports/index.d.ts +5 -0
  38. package/dist/domain/ports/index.js +21 -0
  39. package/dist/domain/ports/logs/find/index.d.ts +2 -0
  40. package/dist/domain/ports/logs/find/index.js +18 -0
  41. package/dist/domain/ports/logs/find/log-file-line-reader.port.d.ts +3 -0
  42. package/dist/domain/ports/logs/find/log-file-line-reader.port.js +2 -0
  43. package/dist/domain/ports/logs/find/log-file-numerator.port.d.ts +3 -0
  44. package/dist/domain/ports/logs/find/log-file-numerator.port.js +2 -0
  45. package/dist/domain/ports/logs/index.d.ts +2 -0
  46. package/dist/domain/ports/logs/index.js +18 -0
  47. package/dist/domain/ports/logs/log-datasource.port.d.ts +10 -0
  48. package/dist/domain/ports/logs/log-datasource.port.js +2 -0
  49. package/dist/domain/ports/system-clock.port.d.ts +8 -0
  50. package/dist/domain/ports/system-clock.port.js +2 -0
  51. package/dist/domain/request/index.d.ts +2 -0
  52. package/dist/domain/request/index.js +18 -0
  53. package/dist/domain/request/log-filter.request.d.ts +9 -0
  54. package/dist/domain/request/log-filter.request.js +2 -0
  55. package/dist/domain/request/save-log.request.d.ts +7 -0
  56. package/dist/domain/request/save-log.request.js +2 -0
  57. package/dist/domain/response/index.d.ts +1 -0
  58. package/dist/domain/response/index.js +17 -0
  59. package/dist/domain/response/log.response.d.ts +8 -0
  60. package/dist/domain/response/log.response.js +2 -0
  61. package/dist/domain/types/fs-rotation-by.type.d.ts +8 -0
  62. package/dist/domain/types/fs-rotation-by.type.js +2 -0
  63. package/dist/domain/types/index.d.ts +1 -0
  64. package/dist/domain/types/index.js +17 -0
  65. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +36 -0
  66. package/dist/domain/value-objects/file-name-pattern.vo.js +53 -0
  67. package/dist/domain/value-objects/file-path.vo.d.ts +91 -0
  68. package/dist/domain/value-objects/file-path.vo.js +100 -0
  69. package/dist/domain/value-objects/file-rotation-policy.vo.d.ts +51 -0
  70. package/dist/domain/value-objects/file-rotation-policy.vo.js +76 -0
  71. package/dist/domain/value-objects/file-size.vo.d.ts +75 -0
  72. package/dist/domain/value-objects/file-size.vo.js +114 -0
  73. package/dist/domain/value-objects/index.d.ts +5 -0
  74. package/dist/domain/value-objects/index.js +21 -0
  75. package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
  76. package/dist/domain/value-objects/log-level.vo.js +13 -0
  77. package/dist/index.d.ts +3 -11
  78. package/dist/index.js +10 -61
  79. package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +79 -0
  80. package/dist/infrastructure/adapters/file-rotator.adapter.js +171 -0
  81. package/dist/infrastructure/adapters/fileSystem-datasource.adapter.d.ts +26 -0
  82. package/dist/infrastructure/adapters/fileSystem-datasource.adapter.js +45 -0
  83. package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.d.ts +6 -0
  84. package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.js +54 -0
  85. package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.d.ts +4 -0
  86. package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.js +53 -0
  87. package/dist/infrastructure/adapters/filesystem-provider.adapter.d.ts +122 -0
  88. package/dist/infrastructure/adapters/filesystem-provider.adapter.js +182 -0
  89. package/dist/infrastructure/adapters/index.d.ts +8 -0
  90. package/dist/infrastructure/adapters/index.js +24 -0
  91. package/dist/infrastructure/adapters/log-stream-writer.adapter.d.ts +80 -0
  92. package/dist/infrastructure/adapters/log-stream-writer.adapter.js +163 -0
  93. package/dist/infrastructure/adapters/system-clock.adapter.d.ts +25 -0
  94. package/dist/infrastructure/adapters/system-clock.adapter.js +30 -0
  95. package/dist/infrastructure/adapters/system-file-path.adapter.d.ts +47 -0
  96. package/dist/infrastructure/adapters/system-file-path.adapter.js +141 -0
  97. package/dist/infrastructure/errors/file-operation.error.d.ts +28 -0
  98. package/dist/infrastructure/errors/file-operation.error.js +54 -0
  99. package/dist/infrastructure/errors/index.d.ts +1 -0
  100. package/dist/infrastructure/errors/index.js +17 -0
  101. package/dist/infrastructure/errors/types/file-operation-error-options.type.d.ts +8 -0
  102. package/dist/infrastructure/errors/types/file-operation-error-options.type.js +2 -0
  103. package/dist/infrastructure/errors/types/file-operation.type.d.ts +1 -0
  104. package/dist/infrastructure/errors/types/file-operation.type.js +2 -0
  105. package/dist/infrastructure/errors/types/fs-error-scope.type.d.ts +1 -0
  106. package/dist/infrastructure/errors/types/fs-error-scope.type.js +2 -0
  107. package/dist/infrastructure/errors/types/index.d.ts +3 -0
  108. package/dist/infrastructure/errors/types/index.js +19 -0
  109. package/dist/infrastructure/filesystem/index.d.ts +1 -0
  110. package/dist/infrastructure/filesystem/index.js +17 -0
  111. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +45 -0
  112. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
  113. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +12 -0
  114. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
  115. package/dist/infrastructure/filesystem/types/index.d.ts +2 -0
  116. package/dist/infrastructure/filesystem/types/index.js +18 -0
  117. package/install.md +520 -0
  118. package/package.json +40 -12
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # @jmlq/logger-plugin-fs
2
+
3
+ Plugin de **sistema de archivos** para [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger). Permite persistir logs en archivos con soporte para rotación automática, manejo de backpressure y configuración flexible de formato y estructura de archivos.
4
+
5
+ ## 📦 Instalación
6
+
7
+ ```bash
8
+ npm install @jmlq/logger @jmlq/logger-plugin-fs
9
+ ```
10
+
11
+ ### Dependencias
12
+
13
+ El plugin requiere como peer dependency:
14
+
15
+ - `@jmlq/logger` >= 0.1.0-alpha.12
16
+
17
+ > **Nota:** Este plugin requiere [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger) como dependencia principal.
18
+
19
+ ## 🚀 Uso rápido
20
+
21
+ ```typescript
22
+ import { LoggerFactory } from "@jmlq/logger";
23
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
24
+
25
+ // Crear datasource de filesystem
26
+ const fsDatasource = createFsDatasource({
27
+ basePath: "./logs",
28
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
29
+ rotation: { by: "day" },
30
+ });
31
+
32
+ // Crear logger usando la factory
33
+ const logger = LoggerFactory.create([fsDatasource]);
34
+
35
+ // Usar el logger
36
+ logger.info("Aplicación iniciada", { timestamp: new Date(), pid: process.pid });
37
+ logger.error("Error de conexión", { service: "database", retries: 3 });
38
+
39
+ // Cierre elegante
40
+ process.on("SIGTERM", async () => {
41
+ await logger.flush();
42
+ await logger.dispose();
43
+ process.exit(0);
44
+ });
45
+ ```
46
+
47
+ ## ⚙️ Configuración del datasource
48
+
49
+ El datasource de filesystem acepta las siguientes opciones de configuración:
50
+
51
+ ### Opciones principales
52
+
53
+ | Opción | Tipo | Descripción | Por defecto |
54
+ | ----------------- | ---------------- | ------------------------------------------------------ | --------------------------- |
55
+ | `basePath` | `string` | Directorio base donde se guardarán los archivos de log | `"./logs"` |
56
+ | `fileNamePattern` | `string` | Patrón para nombrar archivos con tokens de fecha | `"app-{yyyy}{MM}{dd}.log"` |
57
+ | `rotation` | `RotationConfig` | Configuración de política de rotación | `{ by: "day" }` |
58
+ | `mkdir` | `boolean` | Crear directorio base si no existe | `true` |
59
+ | `serializer` | `LogSerializer` | Serializador personalizado para el formato de líneas | Serializer JSON por defecto |
60
+
61
+ ### Configuración de rotación
62
+
63
+ ```typescript
64
+ interface RotationConfig {
65
+ by: "none" | "day" | "size";
66
+ maxSizeMB?: number; // Para rotación por tamaño
67
+ maxFiles?: number; // Límite de archivos rotados
68
+ }
69
+ ```
70
+
71
+ ### Patrones de nombre de archivo
72
+
73
+ Utiliza tokens que se reemplazan con valores de fecha (UTC):
74
+
75
+ - `{yyyy}` - Año completo (ej: 2024)
76
+ - `{MM}` - Mes con ceros (ej: 01, 12)
77
+ - `{dd}` - Día con ceros (ej: 01, 31)
78
+
79
+ **Ejemplos:**
80
+
81
+ - `"app-{yyyy}{MM}{dd}.log"` → `app-20241201.log`
82
+ - `"service-{yyyy}-{MM}-{dd}.log"` → `service-2024-12-01.log`
83
+ - `"application.log"` → `application.log` (sin rotación por fecha)
84
+
85
+ ## 🔁 Políticas de rotación
86
+
87
+ ### Rotación por fecha
88
+
89
+ Crea un archivo nuevo cada día basado en fecha UTC:
90
+
91
+ ```typescript
92
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
93
+
94
+ const datasource = createFsDatasource({
95
+ basePath: "./logs",
96
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
97
+ rotation: { by: "day" },
98
+ onRotate: (oldPath, newPath) => {
99
+ console.log(
100
+ `Log rotado: ${oldPath.absolutePath} → ${newPath.absolutePath}`
101
+ );
102
+ },
103
+ });
104
+ ```
105
+
106
+ ### Rotación por tamaño
107
+
108
+ Rota cuando el archivo alcanza el tamaño máximo especificado:
109
+
110
+ ```typescript
111
+ const datasource = createFsDatasource({
112
+ basePath: "./logs",
113
+ fileNamePattern: "app.log",
114
+ rotation: {
115
+ by: "size",
116
+ maxSizeMB: 50, // Rota al alcanzar 50MB
117
+ maxFiles: 10, // Mantiene máximo 10 archivos rotados
118
+ },
119
+ onRotate: (oldPath, newPath) => {
120
+ console.log(
121
+ `Archivo rotado por tamaño: ${oldPath.absolutePath} → ${newPath.absolutePath}`
122
+ );
123
+ },
124
+ });
125
+ ```
126
+
127
+ ### Sin rotación
128
+
129
+ Mantiene un único archivo que crece indefinidamente:
130
+
131
+ ```typescript
132
+ const datasource = createFsDatasource({
133
+ basePath: "./logs",
134
+ fileNamePattern: "application.log",
135
+ rotation: { by: "none" },
136
+ });
137
+ ```
138
+
139
+ ## 🧩 Ejemplo completo
140
+
141
+ ```typescript
142
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
143
+ import { LogLevel } from "@jmlq/logger";
144
+
145
+ const datasource = createFsDatasource({
146
+ basePath: "./logs",
147
+ fileNamePattern: "app-{yyyy}-{MM}-{dd}.log",
148
+ rotation: { by: "day" },
149
+ mkdir: true,
150
+ serializer: {
151
+ serialize(log: any) {
152
+ return JSON.stringify(log, null, 2);
153
+ },
154
+ },
155
+ onRotate: (oldPath, newPath) => {
156
+ console.log(`Rotación: ${oldPath.absolutePath} → ${newPath.absolutePath}`);
157
+ },
158
+ onError: (err) => {
159
+ console.error("Error en datasource:", err.message);
160
+ },
161
+ });
162
+
163
+ // Guardar logs
164
+ await datasource.save({
165
+ level: LogLevel.INFO,
166
+ message: "Servidor iniciado correctamente",
167
+ timestamp: Date.now(),
168
+ });
169
+
170
+ await datasource.save({
171
+ level: LogLevel.DEBUG,
172
+ message: "Conectando a base de datos...",
173
+ timestamp: Date.now(),
174
+ });
175
+
176
+ // Cierre
177
+ await datasource.flush();
178
+ await datasource.dispose();
179
+ ```
180
+
181
+ ## 🧪 Testing
182
+
183
+ ```typescript
184
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
185
+ import * as fs from "fs";
186
+ import * as path from "path";
187
+
188
+ describe("FileSystem Datasource", () => {
189
+ const testLogsPath = "./test-logs";
190
+
191
+ beforeEach(() => {
192
+ if (fs.existsSync(testLogsPath)) {
193
+ fs.rmSync(testLogsPath, { recursive: true });
194
+ }
195
+ });
196
+
197
+ test("debe crear archivo de log", async () => {
198
+ const datasource = createFsDatasource({
199
+ basePath: testLogsPath,
200
+ fileNamePattern: "test.log",
201
+ rotation: { by: "none" },
202
+ });
203
+
204
+ await datasource.save({
205
+ level: "info",
206
+ message: "Test message",
207
+ timestamp: new Date(),
208
+ });
209
+
210
+ await datasource.flush();
211
+
212
+ const logFile = path.join(testLogsPath, "test.log");
213
+ expect(fs.existsSync(logFile)).toBe(true);
214
+
215
+ const content = fs.readFileSync(logFile, "utf8");
216
+ expect(content).toContain("Test message");
217
+ });
218
+ });
219
+ ```
220
+
221
+ ## 📄 Más Información
222
+
223
+ - **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
224
+ - **[Guía de Instalación](./install.md)** - Configuración paso a paso
225
+ - **[Ejemplos](./examples/)** - Códigos de ejemplo funcionales
226
+
227
+ ## 📄 Licencia
228
+
229
+ MIT © Mauricio Lahuasi
@@ -0,0 +1,426 @@
1
+ # Arquitectura del Plugin - @jmlq/logger-plugin-fs
2
+
3
+ ## Visión General del Plugin
4
+
5
+ `@jmlq/logger-plugin-fs` es un plugin datasource especializado que resuelve la persistencia de logs en sistema de archivos para el ecosistema `@jmlq/logger`. Su arquitectura está diseñada bajo principios de Clean Architecture, proporcionando un sistema robusto de escritura de logs con capacidades avanzadas de rotación automática.
6
+
7
+ El plugin aporta al ecosistema `@jmlq/logger` la capacidad de persistir logs en archivos con gestión inteligente de rotación, asegurando que las aplicaciones puedan mantener historial de logs sin comprometer el rendimiento ni el espacio en disco. Su diseño modular permite diferentes estrategias de rotación y serialización según las necesidades específicas de cada aplicación.
8
+
9
+ Como datasource, el plugin implementa la interfaz `ILogDatasource` de `@jmlq/logger`, actuando como un adaptador entre el logger principal y el sistema de archivos, encapsulando toda la complejidad de gestión de archivos, rotación y escritura asíncrona.
10
+
11
+ ## Principios de Arquitectura
12
+
13
+ ### Clean Architecture
14
+
15
+ El plugin implementa Clean Architecture con separación clara de responsabilidades en capas concéntricas, donde las dependencias fluyen hacia adentro (Infrastructure → Application → Domain).
16
+
17
+ ### Ports & Adapters (Hexagonal Architecture)
18
+
19
+ Utiliza el patrón Ports & Adapters para aislar la lógica de negocio de las implementaciones concretas del filesystem, permitiendo testabilidad y flexibilidad en las implementaciones.
20
+
21
+ ### Separation of Concerns
22
+
23
+ Cada componente tiene una responsabilidad específica y bien definida, desde la gestión de Value Objects hasta la escritura física de archivos.
24
+
25
+ ### Dependency Inversion
26
+
27
+ Las capas internas definen interfaces (ports) que las capas externas implementan, invirtiendo las dependencias y manteniendo la independencia del dominio.
28
+
29
+ ## Estructura del Proyecto
30
+
31
+ ### `/src/domain/` - Capa de Dominio
32
+
33
+ **Responsabilidad**: Contiene la lógica de negocio pura y las abstracciones fundamentales del plugin.
34
+
35
+ **Contenido**:
36
+
37
+ - `/ports/` - Interfaces que definen contratos con infraestructura
38
+ - `/value-objects/` - Objetos de valor inmutables del dominio
39
+
40
+ ### `/src/application/` - Capa de Aplicación
41
+
42
+ **Responsabilidad**: Orquesta los casos de uso del plugin y coordina entre dominio e infraestructura.
43
+
44
+ **Contenido**:
45
+
46
+ - `/dto/` - Data Transfer Objects para casos de uso
47
+ - `/factory/` - Factories para creación de componentes
48
+ - `/services/` - Servicios de aplicación que implementan `ILogDatasource`
49
+ - `/use-cases/` - Casos de uso específicos del plugin
50
+
51
+ ### `/src/infrastructure/` - Capa de Infraestructura
52
+
53
+ **Responsabilidad**: Implementaciones concretas de los ports del dominio y adaptadores específicos.
54
+
55
+ **Contenido**:
56
+
57
+ - `/adapters/` - Implementaciones concretas de ports del dominio
58
+ - `/filesystem/` - Tipos, políticas y ports específicos del filesystem
59
+
60
+ ### `/src/shared/` - Utilitarios Compartidos
61
+
62
+ **Responsabilidad**: Errores y utilidades transversales al plugin.
63
+
64
+ **Contenido**:
65
+
66
+ - `/errors/` - Jerarquía de errores específicos del plugin
67
+
68
+ ## Componentes Internos
69
+
70
+ ### Value Objects del Dominio
71
+
72
+ #### FilePath
73
+
74
+ ```typescript
75
+ class FilePath {
76
+ constructor(props: IFilePathProps);
77
+ get absolutePath(): string;
78
+ get directory(): string;
79
+ get filename(): string;
80
+ get extension(): string;
81
+ get basename(): string;
82
+ equals(other: FilePath): boolean;
83
+ }
84
+ ```
85
+
86
+ **Responsabilidad**: Encapsula una ruta de archivo completa como objeto inmutable, garantizando consistencia en la representación de paths a través del sistema.
87
+
88
+ #### FileSize
89
+
90
+ ```typescript
91
+ class FileSize {
92
+ constructor(bytes: number);
93
+ get bytes(): number;
94
+ get megabytes(): number;
95
+ isGreaterThan(other: FileSize): boolean;
96
+ equals(other: FileSize): boolean;
97
+ }
98
+ ```
99
+
100
+ **Responsabilidad**: Representa el tamaño de archivos como concepto del dominio, proporcionando métodos de comparación y conversión de unidades.
101
+
102
+ ### Value Objects de Infraestructura
103
+
104
+ #### FileNamePattern
105
+
106
+ ```typescript
107
+ class FileNamePattern {
108
+ constructor(pattern: string);
109
+ get pattern(): string;
110
+ hasDateTokens(): boolean;
111
+ getTokens(): string[];
112
+ }
113
+ ```
114
+
115
+ **Responsabilidad**: Encapsula patrones de nombres de archivo con placeholders para fechas, permitiendo generación dinámica de nombres basados en timestamps.
116
+
117
+ #### RotationPolicy
118
+
119
+ ```typescript
120
+ class RotationPolicy {
121
+ constructor(by: FsRotationBy, maxSizeMB?: number, maxFiles?: number);
122
+ get by(): FsRotationBy;
123
+ get maxSizeMB(): number | undefined;
124
+ get maxFiles(): number | undefined;
125
+ shouldRotateBySize(currentSize: FileSize): boolean;
126
+ }
127
+ ```
128
+
129
+ **Responsabilidad**: Define las reglas de rotación de archivos, encapsulando la lógica de cuándo y cómo rotar archivos según diferentes estrategias.
130
+
131
+ ### Tipos del Sistema
132
+
133
+ #### FsRotationBy
134
+
135
+ ```typescript
136
+ type FsRotationBy = "none" | "day" | "size";
137
+ ```
138
+
139
+ **Responsabilidad**: Define las estrategias de rotación disponibles en el sistema.
140
+
141
+ ### Adaptadores de Infraestructura
142
+
143
+ #### FsWriterAdapter
144
+
145
+ ```typescript
146
+ class FsWriterAdapter implements IStreamWriterPort {
147
+ async write(data: string): Promise<boolean>;
148
+ async open(path: FilePath): Promise<void>;
149
+ async close(): Promise<void>;
150
+ async flush(): Promise<void>;
151
+ isOpen(): boolean;
152
+ getCurrentPath(): FilePath | null;
153
+ }
154
+ ```
155
+
156
+ **Responsabilidad**: Maneja la escritura física en archivos usando Node.js WriteStream, con gestión de estado y control de flujo.
157
+
158
+ #### FileRotatorAdapter
159
+
160
+ ```typescript
161
+ class FileRotatorAdapter implements IFileRotatorPort {
162
+ getCurrentPath(): FilePath | null;
163
+ getExpectedPathForDate(date: Date): FilePath;
164
+ async getFileSize(filePath: FilePath): Promise<FileSize>;
165
+ async shouldRotate(
166
+ policy: RotationPolicy,
167
+ currentDate: Date
168
+ ): Promise<boolean>;
169
+ }
170
+ ```
171
+
172
+ **Responsabilidad**: Implementa la lógica de rotación de archivos, generación de paths basados en fechas y evaluación de políticas de rotación.
173
+
174
+ #### FsProviderAdapter
175
+
176
+ ```typescript
177
+ class FsProviderAdapter implements IFsProviderPort
178
+ ```
179
+
180
+ **Responsabilidad**: Abstrae operaciones del filesystem (stat, readdir, mkdir) proporcionando una interfaz uniforme para el dominio.
181
+
182
+ #### NodeClockAdapter
183
+
184
+ ```typescript
185
+ class NodeClockAdapter implements IClockPort
186
+ ```
187
+
188
+ **Responsabilidad**: Proporciona acceso al tiempo del sistema de forma abstraída.
189
+
190
+ #### NodeFilePathAdapter
191
+
192
+ ```typescript
193
+ class NodeFilePathAdapter implements IFilePathAdapterPort
194
+ ```
195
+
196
+ **Responsabilidad**: Maneja operaciones de manipulación de paths usando el módulo `path` de Node.js.
197
+
198
+ ### Services de Aplicación
199
+
200
+ #### FsDatasourceService
201
+
202
+ ```typescript
203
+ class FsDatasourceService implements ILogDatasource {
204
+ async save(log: ILogProps): Promise<void>;
205
+ async flush(): Promise<void>;
206
+ async dispose(): Promise<void>;
207
+ }
208
+ ```
209
+
210
+ **Responsabilidad**: Implementa la interfaz `ILogDatasource` del logger principal, actuando como punto de entrada del plugin y delegando a los casos de uso correspondientes.
211
+
212
+ ### Factory Principal
213
+
214
+ #### createFsDatasource
215
+
216
+ ```typescript
217
+ function createFsDatasource(
218
+ options: IFilesystemDatasourceOptions
219
+ ): ILogDatasource;
220
+ ```
221
+
222
+ **Responsabilidad**: Factory function que ensambla todos los componentes del plugin, creando e inyectando dependencias según la configuración proporcionada.
223
+
224
+ ## Política de Rotación
225
+
226
+ ### Estrategias Implementadas
227
+
228
+ #### Rotación por Día (`"day"`)
229
+
230
+ La rotación se basa en cambios de fecha. El sistema evalúa si el archivo actual corresponde a la fecha presente:
231
+
232
+ 1. **Evaluación**: Compara el path actual con el path esperado para la fecha actual
233
+ 2. **Decisión**: Si difieren, se requiere rotación
234
+ 3. **Proceso**: Cierra el stream actual y abre uno nuevo con el path actualizado
235
+
236
+ #### Rotación por Tamaño (`"size"`)
237
+
238
+ La rotación se activa cuando el archivo supera el límite configurado:
239
+
240
+ 1. **Evaluación**: Compara el tamaño actual del archivo con `maxSizeMB` usando `FileSize.isGreaterThan()`
241
+ 2. **Decisión**: Se rota cuando el tamaño es mayor o igual al límite
242
+ 3. **Proceso**: Se crea un nuevo archivo y el anterior se mantiene según `maxFiles`
243
+
244
+ #### Sin Rotación (`"none"`)
245
+
246
+ El archivo crece indefinidamente sin intervención del sistema de rotación.
247
+
248
+ ### Generación de Nombres de Archivos
249
+
250
+ El sistema utiliza `FileNamePattern` para generar nombres dinámicamente:
251
+
252
+ **Placeholders soportados**:
253
+
254
+ - `{yyyy}` - Año de 4 dígitos
255
+ - `{MM}` - Mes con padding (01-12)
256
+ - `{dd}` - Día con padding (01-31)
257
+ - `{HH}` - Hora con padding (00-23)
258
+ - `{mm}` - Minutos con padding (00-59)
259
+ - `{ss}` - Segundos con padding (00-59)
260
+
261
+ **Proceso**:
262
+
263
+ 1. `FileRotatorAdapter.buildPathForDate()` toma la fecha actual
264
+ 2. Reemplaza cada placeholder con el valor correspondiente de la fecha
265
+ 3. Combina con `basePath` usando `NodeFilePathAdapter`
266
+ 4. Retorna un `FilePath` completo
267
+
268
+ ## Flujo Interno del Datasource
269
+
270
+ ### Pipeline de Escritura de Logs
271
+
272
+ ```
273
+ ILogProps → ISaveLogDto → PersistLogUseCase → Filesystem
274
+ ↓ ↓ ↓ ↓
275
+ [Logger] [Application] [Domain] [Infrastructure]
276
+ ```
277
+
278
+ ### Secuencia Detallada
279
+
280
+ 1. **Recepción**: `FsDatasourceService.save()` recibe `ILogProps` desde el logger principal
281
+
282
+ 2. **Transformación**: Se encapsula en `ISaveLogDto` para el caso de uso
283
+
284
+ 3. **Orquestación**: `PersistLogUseCase.execute()` coordina el proceso:
285
+
286
+ - Asegura que el directorio existe (`EnsureDirectoryUseCase`)
287
+ - Evalúa necesidad de rotación (`RotateIfNeededUseCase`)
288
+ - Verifica estado del stream de escritura
289
+ - Delega escritura (`AppendLogUseCase`)
290
+
291
+ 4. **Evaluación de Rotación**: `RotateIfNeededUseCase`:
292
+
293
+ - Consulta `FileRotatorAdapter.shouldRotate()`
294
+ - Si requiere rotación, cierra stream actual y abre uno nuevo
295
+ - Ejecuta callback `onRotate` si está configurado
296
+
297
+ 5. **Escritura**: `AppendLogUseCase`:
298
+
299
+ - Serializa el log usando `IFsSerializer`
300
+ - Escribe en el stream actual via `FsWriterAdapter`
301
+
302
+ 6. **Gestión de Stream**: `FsWriterAdapter`:
303
+ - Maneja WriteStream de Node.js
304
+ - Controla backpressure y drenaje
305
+ - Gestiona apertura/cierre de archivos
306
+
307
+ ## Dependencias entre Capas
308
+
309
+ ```
310
+ ┌─────────────────────────────────────────────────────┐
311
+ │ Infrastructure │
312
+ │ ┌─────────────────┐ ┌─────────────────────────┐ │
313
+ │ │ Adapters │ │ Filesystem │ │
314
+ │ │ - FsWriter │ │ - RotationPolicy │ │
315
+ │ │ - FileRotator │ │ - FileNamePattern │ │
316
+ │ │ - FsProvider │ │ - Types & Ports │ │
317
+ │ │ - NodeClock │ │ │ │
318
+ │ │ - NodeFilePath │ │ │ │
319
+ │ └─────────────────┘ └─────────────────────────┘ │
320
+ └─────────────────────────────────────────────────────┘
321
+
322
+
323
+ ┌─────────────────────────────────────────────────────┐
324
+ │ Application │
325
+ │ ┌─────────────────┐ ┌─────────────────────────┐ │
326
+ │ │ Use Cases │ │ Factory & Service │ │
327
+ │ │ - PersistLog │ │ - createFsDatasource │ │
328
+ │ │ - AppendLog │ │ - FsDatasourceService │ │
329
+ │ │ - RotateIfNeed │ │ │ │
330
+ │ │ - EnsureDir │ │ │ │
331
+ │ └─────────────────┘ └─────────────────────────┘ │
332
+ └─────────────────────────────────────────────────────┘
333
+
334
+
335
+ ┌─────────────────────────────────────────────────────┐
336
+ │ Domain │
337
+ │ ┌─────────────────┐ ┌─────────────────────────┐ │
338
+ │ │ Value Objects │ │ Ports │ │
339
+ │ │ - FilePath │ │ - IStreamWriterPort │ │
340
+ │ │ - FileSize │ │ - IFileRotatorPort │ │
341
+ │ │ │ │ - IFsProviderPort │ │
342
+ │ │ │ │ - IClockPort │ │
343
+ │ └─────────────────┘ └─────────────────────────┘ │
344
+ └─────────────────────────────────────────────────────┘
345
+ ```
346
+
347
+ ## Patrones Técnicos Implementados
348
+
349
+ ### Adapter Pattern
350
+
351
+ Los adaptadores (`FsWriterAdapter`, `FileRotatorAdapter`, etc.) implementan interfaces del dominio, adaptando APIs externas (Node.js filesystem) a las necesidades del plugin.
352
+
353
+ ### Strategy Pattern
354
+
355
+ `RotationPolicy` encapsula diferentes estrategias de rotación (`"day"`, `"size"`, `"none"`) que pueden intercambiarse dinámicamente.
356
+
357
+ ### Value Object Pattern
358
+
359
+ `FilePath`, `FileSize`, `FileNamePattern` y `RotationPolicy` implementan el patrón Value Object, proporcionando inmutabilidad y encapsulación de conceptos del dominio.
360
+
361
+ ### Factory Pattern
362
+
363
+ `createFsDatasource` implementa el patrón Factory, encapsulando la complejidad de creación y configuración de dependencias.
364
+
365
+ ### Port & Adapter Pattern
366
+
367
+ Las interfaces en `/domain/ports/` definen contratos que las implementaciones en `/infrastructure/adapters/` cumplen, permitiendo inversión de dependencias.
368
+
369
+ ## Interacción con @jmlq/logger
370
+
371
+ ### Integración como Datasource
372
+
373
+ El plugin se integra implementando `ILogDatasource`:
374
+
375
+ ```typescript
376
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
377
+ import { LoggerFactory } from "@jmlq/logger";
378
+
379
+ const fsDatasource = createFsDatasource({
380
+ basePath: "./logs",
381
+ rotation: { by: "day" },
382
+ });
383
+
384
+ const logger = LoggerFactory.create({
385
+ datasources: [fsDatasource],
386
+ });
387
+ ```
388
+
389
+ ### Flujo de Integración
390
+
391
+ 1. `LoggerFactory` registra el datasource creado por `createFsDatasource`
392
+ 2. Cuando se ejecuta `logger.info()`, `logger.error()`, etc., el logger principal invoca `FsDatasourceService.save()`
393
+ 3. El plugin procesa el log a través de su arquitectura interna
394
+ 4. El resultado se persiste en el filesystem según la configuración
395
+
396
+ ### Lifecycle Management
397
+
398
+ ```typescript
399
+ // Flush de buffers pendientes
400
+ await fsDatasource.flush();
401
+
402
+ // Cierre limpio de recursos
403
+ await fsDatasource.dispose();
404
+ ```
405
+
406
+ ## Consideraciones Técnicas
407
+
408
+ ### Gestión de Concurrencia
409
+
410
+ El plugin maneja escritura asíncrona usando WriteStream de Node.js con control de backpressure a través de eventos `drain` y gestión de estado interno.
411
+
412
+ ### Manejo de Errores
413
+
414
+ La arquitectura proporciona tres niveles de error:
415
+
416
+ - `FsPluginError`: Errores generales del plugin
417
+ - `FileOperationError`: Errores específicos de operaciones de archivo
418
+ - `RotationError`: Errores relacionados con rotación de archivos
419
+
420
+ ### Limitaciones Arquitecturales
421
+
422
+ 1. **Dependencia de Node.js**: El plugin está acoplado al runtime de Node.js para operaciones de filesystem
423
+ 2. **Rotación Síncrona por Fecha**: La evaluación de rotación por día se basa en comparación de paths, no en metadata de archivos
424
+ 3. **Sin Compresión**: No incluye compresión automática de archivos rotados
425
+ 4. **Estrategias de Rotación Limitadas**: Solo soporta rotación por día, tamaño o ninguna
426
+ 5. **Sin Limpieza Automática**: La limpieza de archivos antiguos debe implementarse externamente usando `maxFiles` como referencia
@@ -0,0 +1 @@
1
+ export * from "./rotate-if-needed.request";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./rotate-if-needed.request"), exports);
@@ -0,0 +1,10 @@
1
+ import { FileRotationPolicy } from "../../domain/value-objects";
2
+ /**
3
+ * DTO de entrada para el caso de uso RotateIfNeeded.
4
+ * - currentDate: fecha/hora actual (inyectada desde un clock para tests deterministas)
5
+ * - rotationPolicy: política de rotación (VO de dominio)
6
+ */
7
+ export interface RotateIfNeededRequest {
8
+ currentDate: Date;
9
+ rotationPolicy: FileRotationPolicy;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });