@jmlq/logger-plugin-fs 0.1.0-alpha.6 → 0.1.0-alpha.8

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 (120) hide show
  1. package/README.md +165 -117
  2. package/architecture.md +426 -0
  3. package/dist/application/dto/index.d.ts +2 -0
  4. package/dist/application/dto/index.js +2 -0
  5. package/dist/application/dto/rotation-check.dto.d.ts +5 -0
  6. package/dist/application/dto/save-log.dto.d.ts +3 -3
  7. package/dist/application/dto/write-operation.dto.d.ts +5 -0
  8. package/dist/application/factory/create-fs-datasource.factory.d.ts +3 -0
  9. package/dist/application/factory/create-fs-datasource.factory.js +26 -0
  10. package/dist/application/factory/index.d.ts +1 -0
  11. package/dist/{presentation → application}/factory/index.js +1 -1
  12. package/dist/application/services/fs-datasource.service.d.ts +7 -5
  13. package/dist/application/services/fs-datasource.service.js +6 -9
  14. package/dist/application/use-cases/append-log.use-case.d.ts +12 -0
  15. package/dist/application/use-cases/append-log.use-case.js +26 -0
  16. package/dist/application/use-cases/ensure-directory.use-case.d.ts +11 -0
  17. package/dist/application/use-cases/ensure-directory.use-case.js +27 -0
  18. package/dist/application/use-cases/index.d.ts +4 -3
  19. package/dist/application/use-cases/index.js +4 -3
  20. package/dist/application/use-cases/persist-log.use-case.d.ts +19 -0
  21. package/dist/application/use-cases/persist-log.use-case.js +47 -0
  22. package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +17 -0
  23. package/dist/application/use-cases/rotate-if-needed.use-case.js +28 -0
  24. package/dist/domain/ports/clock.port.d.ts +8 -0
  25. package/dist/domain/ports/file-path-adapter.port.d.ts +33 -0
  26. package/dist/domain/ports/fs-provider.port.d.ts +61 -0
  27. package/dist/domain/ports/fs-provider.port.js +2 -0
  28. package/dist/domain/ports/index.d.ts +4 -0
  29. package/dist/domain/{contracts → ports}/index.js +3 -3
  30. package/dist/domain/ports/stream-writer.port.d.ts +42 -0
  31. package/dist/domain/ports/stream-writer.port.js +2 -0
  32. package/dist/domain/value-objects/file-path.vo.d.ts +27 -0
  33. package/dist/domain/value-objects/file-path.vo.js +59 -0
  34. package/dist/domain/value-objects/file-size.vo.d.ts +14 -0
  35. package/dist/domain/value-objects/file-size.vo.js +57 -0
  36. package/dist/domain/value-objects/index.d.ts +2 -2
  37. package/dist/domain/value-objects/index.js +2 -2
  38. package/dist/index.d.ts +4 -3
  39. package/dist/index.js +14 -5
  40. package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +20 -0
  41. package/dist/infrastructure/adapters/file-rotator.adapter.js +105 -0
  42. package/dist/infrastructure/adapters/fs-provider.adapter.d.ts +17 -0
  43. package/dist/infrastructure/{fs/fs-provider.js → adapters/fs-provider.adapter.js} +41 -19
  44. package/dist/infrastructure/adapters/fs-writer.adapter.d.ts +13 -0
  45. package/dist/infrastructure/adapters/fs-writer.adapter.js +71 -0
  46. package/dist/infrastructure/{fs → adapters}/index.d.ts +2 -2
  47. package/dist/infrastructure/{fs → adapters}/index.js +2 -2
  48. package/dist/infrastructure/adapters/node-clock.adapter.d.ts +4 -0
  49. package/dist/infrastructure/{fs → adapters}/node-clock.adapter.js +0 -1
  50. package/dist/infrastructure/adapters/node-file-path.adapter.d.ts +16 -0
  51. package/dist/infrastructure/adapters/node-file-path.adapter.js +117 -0
  52. package/dist/infrastructure/filesystem/index.d.ts +4 -0
  53. package/dist/infrastructure/filesystem/index.js +20 -0
  54. package/dist/infrastructure/filesystem/polices/index.d.ts +1 -0
  55. package/dist/infrastructure/filesystem/polices/index.js +5 -0
  56. package/dist/infrastructure/filesystem/polices/rotation-policy.d.ts +29 -0
  57. package/dist/infrastructure/filesystem/polices/rotation-policy.js +55 -0
  58. package/dist/infrastructure/filesystem/ports/file-rotator.port.d.ts +32 -0
  59. package/dist/infrastructure/filesystem/ports/file-rotator.port.js +2 -0
  60. package/dist/infrastructure/filesystem/ports/index.d.ts +1 -0
  61. package/dist/{domain/types → infrastructure/filesystem/ports}/index.js +1 -1
  62. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +49 -0
  63. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
  64. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +19 -0
  65. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
  66. package/dist/infrastructure/filesystem/types/filesystem-serializer.type.d.ts +10 -0
  67. package/dist/infrastructure/filesystem/types/filesystem-serializer.type.js +2 -0
  68. package/dist/infrastructure/filesystem/types/index.d.ts +3 -0
  69. package/dist/infrastructure/filesystem/types/index.js +19 -0
  70. package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.d.ts +22 -0
  71. package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.js +37 -0
  72. package/dist/infrastructure/filesystem/value-objects/index.d.ts +1 -0
  73. package/dist/infrastructure/{datasources → filesystem/value-objects}/index.js +1 -1
  74. package/dist/shared/errors/file-operation.error.d.ts +12 -0
  75. package/dist/shared/errors/file-operation.error.js +32 -0
  76. package/dist/shared/errors/fs-plugin.error.d.ts +4 -0
  77. package/dist/shared/errors/fs-plugin.error.js +11 -0
  78. package/dist/shared/errors/index.d.ts +3 -0
  79. package/dist/shared/errors/index.js +19 -0
  80. package/dist/shared/errors/rotation.error.d.ts +10 -0
  81. package/dist/shared/errors/rotation.error.js +25 -0
  82. package/install.md +520 -0
  83. package/package.json +38 -21
  84. package/LICENSE +0 -21
  85. package/dist/application/use-cases/append-log.usecase.d.ts +0 -7
  86. package/dist/application/use-cases/append-log.usecase.js +0 -19
  87. package/dist/application/use-cases/persist-log.usecase.d.ts +0 -23
  88. package/dist/application/use-cases/persist-log.usecase.js +0 -74
  89. package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +0 -10
  90. package/dist/application/use-cases/rotate-if-needed.usecase.js +0 -39
  91. package/dist/domain/contracts/clock.contract.d.ts +0 -3
  92. package/dist/domain/contracts/file-rotator.contract.d.ts +0 -7
  93. package/dist/domain/contracts/index.d.ts +0 -4
  94. package/dist/domain/contracts/serializer.contract.d.ts +0 -3
  95. package/dist/domain/contracts/stream-writer.port.d.ts +0 -6
  96. package/dist/domain/types/index.d.ts +0 -1
  97. package/dist/domain/types/options.type.d.ts +0 -11
  98. package/dist/domain/types/options.type.js +0 -5
  99. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +0 -4
  100. package/dist/domain/value-objects/file-name-pattern.vo.js +0 -14
  101. package/dist/domain/value-objects/rotation-policy.vo.d.ts +0 -7
  102. package/dist/domain/value-objects/rotation-policy.vo.js +0 -20
  103. package/dist/infrastructure/datasources/fs.datasource.d.ts +0 -17
  104. package/dist/infrastructure/datasources/fs.datasource.js +0 -84
  105. package/dist/infrastructure/datasources/index.d.ts +0 -1
  106. package/dist/infrastructure/fs/file-rotator.adapter.d.ts +0 -22
  107. package/dist/infrastructure/fs/file-rotator.adapter.js +0 -51
  108. package/dist/infrastructure/fs/fs-provider.d.ts +0 -15
  109. package/dist/infrastructure/fs/fs-writer.adapter.d.ts +0 -10
  110. package/dist/infrastructure/fs/fs-writer.adapter.js +0 -26
  111. package/dist/infrastructure/fs/node-clock.adapter.d.ts +0 -4
  112. package/dist/infrastructure/fs/path-utils.d.ts +0 -6
  113. package/dist/infrastructure/fs/path-utils.js +0 -26
  114. package/dist/presentation/factory/create-fs-datasource.d.ts +0 -15
  115. package/dist/presentation/factory/create-fs-datasource.js +0 -39
  116. package/dist/presentation/factory/index.d.ts +0 -1
  117. /package/dist/{domain/contracts/clock.contract.js → application/dto/rotation-check.dto.js} +0 -0
  118. /package/dist/{domain/contracts/file-rotator.contract.js → application/dto/write-operation.dto.js} +0 -0
  119. /package/dist/domain/{contracts/serializer.contract.js → ports/clock.port.js} +0 -0
  120. /package/dist/domain/{contracts/stream-writer.port.js → ports/file-path-adapter.port.js} +0 -0
@@ -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
@@ -1 +1,3 @@
1
+ export * from "./rotation-check.dto";
1
2
  export * from "./save-log.dto";
3
+ export * from "./write-operation.dto";
@@ -14,4 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./rotation-check.dto"), exports);
17
18
  __exportStar(require("./save-log.dto"), exports);
19
+ __exportStar(require("./write-operation.dto"), exports);
@@ -0,0 +1,5 @@
1
+ export interface IRotationCheckDto {
2
+ currentPath: string;
3
+ currentDate: Date;
4
+ currentSizeBytes: number;
5
+ }
@@ -1,4 +1,4 @@
1
- import type { ILog } from "@jmlq/logger";
2
- export interface SaveLogDTO {
3
- log: ILog;
1
+ import { ILogProps } from "@jmlq/logger";
2
+ export interface ISaveLogDto {
3
+ log: ILogProps;
4
4
  }
@@ -0,0 +1,5 @@
1
+ export interface IWriteOperationDto {
2
+ data: string;
3
+ targetPath: string;
4
+ append?: boolean;
5
+ }
@@ -0,0 +1,3 @@
1
+ import { ILogDatasource } from "@jmlq/logger";
2
+ import { IFilesystemDatasourceOptions } from "../../infrastructure/filesystem";
3
+ export declare function createFsDatasource(options: IFilesystemDatasourceOptions): ILogDatasource;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFsDatasource = createFsDatasource;
4
+ const adapters_1 = require("../../infrastructure/adapters");
5
+ const use_cases_1 = require("../use-cases");
6
+ const services_1 = require("../services");
7
+ const filesystem_1 = require("../../infrastructure/filesystem");
8
+ function createFsDatasource(options) {
9
+ // Crear Value Objects
10
+ const fileNamePattern = new filesystem_1.FileNamePattern(options.fileNamePattern || "app-{yyyy}{MM}{dd}.log");
11
+ const rotationPolicy = new filesystem_1.RotationPolicy(options.rotation?.by || "day", options.rotation?.maxSizeMB, options.rotation?.maxFiles);
12
+ // Crear adaptadores
13
+ const clockAdapter = new adapters_1.NodeClockAdapter();
14
+ const filePathAdapter = new adapters_1.NodeFilePathAdapter();
15
+ const fsProviderAdapter = new adapters_1.FsProviderAdapter();
16
+ const streamWriterAdapter = new adapters_1.FsWriterAdapter();
17
+ const fileRotatorAdapter = new adapters_1.FileRotatorAdapter(fsProviderAdapter, filePathAdapter, fileNamePattern, options.basePath);
18
+ // Crear casos de uso
19
+ const ensureDirectoryUseCase = new use_cases_1.EnsureDirectoryUseCase(fsProviderAdapter, options.basePath, options.mkdir);
20
+ const appendLogUseCase = new use_cases_1.AppendLogUseCase(streamWriterAdapter, options.serializer);
21
+ const rotateIfNeededUseCase = new use_cases_1.RotateIfNeededUseCase(fileRotatorAdapter, streamWriterAdapter, options.onRotate);
22
+ const persistLogUseCase = new use_cases_1.PersistLogUseCase(clockAdapter, fileRotatorAdapter, streamWriterAdapter, rotationPolicy, rotateIfNeededUseCase, appendLogUseCase, ensureDirectoryUseCase, options.onError);
23
+ // Inicializar directorio
24
+ ensureDirectoryUseCase.execute();
25
+ return new services_1.FsDatasourceService(persistLogUseCase, streamWriterAdapter);
26
+ }
@@ -0,0 +1 @@
1
+ export * from "./create-fs-datasource.factory";
@@ -14,4 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./create-fs-datasource"), exports);
17
+ __exportStar(require("./create-fs-datasource.factory"), exports);
@@ -1,9 +1,11 @@
1
- import type { ILogDatasource, ILog } from "@jmlq/logger";
2
- import { PersistLogUseCase } from "../use-cases";
1
+ import { ILogProps, ILogDatasource } from "@jmlq/logger";
2
+ import { IPersistLogUseCase } from "../use-cases";
3
+ import { IStreamWriterPort } from "../../domain/ports";
3
4
  export declare class FsDatasourceService implements ILogDatasource {
4
- private readonly persistUC;
5
- constructor(persistUC: PersistLogUseCase);
6
- save(log: ILog): Promise<void>;
5
+ private readonly persistLogUseCase;
6
+ private readonly streamWriterPort;
7
+ constructor(persistLogUseCase: IPersistLogUseCase, streamWriterPort: IStreamWriterPort);
8
+ save(log: ILogProps): Promise<void>;
7
9
  flush(): Promise<void>;
8
10
  dispose(): Promise<void>;
9
11
  }
@@ -2,21 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FsDatasourceService = void 0;
4
4
  class FsDatasourceService {
5
- constructor(persistUC) {
6
- this.persistUC = persistUC;
5
+ constructor(persistLogUseCase, streamWriterPort) {
6
+ this.persistLogUseCase = persistLogUseCase;
7
+ this.streamWriterPort = streamWriterPort;
7
8
  }
8
- // Guarda un log (core llama esto). Orquesta vía PersistLogUseCase.
9
9
  async save(log) {
10
- const dto = { log };
11
- await this.persistUC.execute(dto);
10
+ await this.persistLogUseCase.execute({ log });
12
11
  }
13
- // Espera drenaje del buffer (útil para shutdown)
14
12
  async flush() {
15
- await this.persistUC.flush();
13
+ await this.streamWriterPort.flush();
16
14
  }
17
- // Cierra recursos (stream)
18
15
  async dispose() {
19
- await this.persistUC.dispose();
16
+ await this.streamWriterPort.close();
20
17
  }
21
18
  }
22
19
  exports.FsDatasourceService = FsDatasourceService;
@@ -0,0 +1,12 @@
1
+ import { ISaveLogDto } from "../dto/save-log.dto";
2
+ import { IStreamWriterPort } from "../../domain/ports/stream-writer.port";
3
+ import { IFsSerializer } from "../../infrastructure/filesystem";
4
+ export interface IAppendLogUseCase {
5
+ execute(dto: ISaveLogDto): Promise<void>;
6
+ }
7
+ export declare class AppendLogUseCase implements IAppendLogUseCase {
8
+ private readonly streamWriterPort;
9
+ private readonly serializer?;
10
+ constructor(streamWriterPort: IStreamWriterPort, serializer?: IFsSerializer | undefined);
11
+ execute(dto: ISaveLogDto): Promise<void>;
12
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppendLogUseCase = void 0;
4
+ class AppendLogUseCase {
5
+ constructor(streamWriterPort, serializer) {
6
+ this.streamWriterPort = streamWriterPort;
7
+ this.serializer = serializer;
8
+ }
9
+ async execute(dto) {
10
+ const { log } = dto;
11
+ let serializedData;
12
+ if (this.serializer) {
13
+ serializedData = this.serializer.serialize(log);
14
+ }
15
+ else {
16
+ // Serialización por defecto
17
+ serializedData = JSON.stringify(log);
18
+ }
19
+ // Agregar salto de línea si no lo tiene
20
+ if (!serializedData.endsWith("\n")) {
21
+ serializedData += "\n";
22
+ }
23
+ await this.streamWriterPort.write(serializedData);
24
+ }
25
+ }
26
+ exports.AppendLogUseCase = AppendLogUseCase;
@@ -0,0 +1,11 @@
1
+ import { IFsProviderPort } from "../../domain/ports";
2
+ export interface IEnsureDirectoryUseCase {
3
+ execute(): Promise<void>;
4
+ }
5
+ export declare class EnsureDirectoryUseCase implements IEnsureDirectoryUseCase {
6
+ private readonly fsProvider;
7
+ private readonly basePath;
8
+ private readonly createIfNotExists;
9
+ constructor(fsProvider: IFsProviderPort, basePath: string, createIfNotExists?: boolean);
10
+ execute(): Promise<void>;
11
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnsureDirectoryUseCase = void 0;
4
+ const errors_1 = require("../../shared/errors");
5
+ class EnsureDirectoryUseCase {
6
+ constructor(fsProvider, basePath, createIfNotExists = true) {
7
+ this.fsProvider = fsProvider;
8
+ this.basePath = basePath;
9
+ this.createIfNotExists = createIfNotExists;
10
+ }
11
+ async execute() {
12
+ if (!this.createIfNotExists) {
13
+ return;
14
+ }
15
+ try {
16
+ // El basePath ES el directorio que queremos crear
17
+ const exists = await this.fsProvider.exists(this.basePath);
18
+ if (!exists) {
19
+ await this.fsProvider.mkdir(this.basePath, { recursive: true });
20
+ }
21
+ }
22
+ catch (error) {
23
+ throw errors_1.FileOperationError.createForMkdir(this.basePath, error);
24
+ }
25
+ }
26
+ }
27
+ exports.EnsureDirectoryUseCase = EnsureDirectoryUseCase;
@@ -1,3 +1,4 @@
1
- export * from "./rotate-if-needed.usecase";
2
- export * from "./append-log.usecase";
3
- export * from "./persist-log.usecase";
1
+ export * from "./append-log.use-case";
2
+ export * from "./ensure-directory.use-case";
3
+ export * from "./persist-log.use-case";
4
+ export * from "./rotate-if-needed.use-case";
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./rotate-if-needed.usecase"), exports);
18
- __exportStar(require("./append-log.usecase"), exports);
19
- __exportStar(require("./persist-log.usecase"), exports);
17
+ __exportStar(require("./append-log.use-case"), exports);
18
+ __exportStar(require("./ensure-directory.use-case"), exports);
19
+ __exportStar(require("./persist-log.use-case"), exports);
20
+ __exportStar(require("./rotate-if-needed.use-case"), exports);