@jmlq/logger-plugin-fs 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (119) hide show
  1. package/README.md +167 -153
  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/infrastructure/{datasources → 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/{domain/types → infrastructure/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 +39 -21
  84. package/dist/application/use-cases/append-log.usecase.d.ts +0 -7
  85. package/dist/application/use-cases/append-log.usecase.js +0 -19
  86. package/dist/application/use-cases/persist-log.usecase.d.ts +0 -23
  87. package/dist/application/use-cases/persist-log.usecase.js +0 -74
  88. package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +0 -10
  89. package/dist/application/use-cases/rotate-if-needed.usecase.js +0 -39
  90. package/dist/domain/contracts/clock.contract.d.ts +0 -3
  91. package/dist/domain/contracts/file-rotator.contract.d.ts +0 -7
  92. package/dist/domain/contracts/index.d.ts +0 -4
  93. package/dist/domain/contracts/serializer.contract.d.ts +0 -3
  94. package/dist/domain/contracts/stream-writer.port.d.ts +0 -6
  95. package/dist/domain/types/index.d.ts +0 -1
  96. package/dist/domain/types/options.type.d.ts +0 -11
  97. package/dist/domain/types/options.type.js +0 -5
  98. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +0 -4
  99. package/dist/domain/value-objects/file-name-pattern.vo.js +0 -14
  100. package/dist/domain/value-objects/rotation-policy.vo.d.ts +0 -7
  101. package/dist/domain/value-objects/rotation-policy.vo.js +0 -20
  102. package/dist/infrastructure/datasources/fs.datasource.d.ts +0 -17
  103. package/dist/infrastructure/datasources/fs.datasource.js +0 -84
  104. package/dist/infrastructure/datasources/index.d.ts +0 -1
  105. package/dist/infrastructure/fs/file-rotator.adapter.d.ts +0 -22
  106. package/dist/infrastructure/fs/file-rotator.adapter.js +0 -51
  107. package/dist/infrastructure/fs/fs-provider.d.ts +0 -15
  108. package/dist/infrastructure/fs/fs-writer.adapter.d.ts +0 -10
  109. package/dist/infrastructure/fs/fs-writer.adapter.js +0 -26
  110. package/dist/infrastructure/fs/node-clock.adapter.d.ts +0 -4
  111. package/dist/infrastructure/fs/path-utils.d.ts +0 -6
  112. package/dist/infrastructure/fs/path-utils.js +0 -26
  113. package/dist/presentation/factory/create-fs-datasource.d.ts +0 -15
  114. package/dist/presentation/factory/create-fs-datasource.js +0 -39
  115. package/dist/presentation/factory/index.d.ts +0 -1
  116. /package/dist/{domain/contracts/clock.contract.js → application/dto/rotation-check.dto.js} +0 -0
  117. /package/dist/{domain/contracts/file-rotator.contract.js → application/dto/write-operation.dto.js} +0 -0
  118. /package/dist/domain/{contracts/serializer.contract.js → ports/clock.port.js} +0 -0
  119. /package/dist/domain/{contracts/stream-writer.port.js → ports/file-path-adapter.port.js} +0 -0
package/README.md CHANGED
@@ -1,208 +1,222 @@
1
1
  # @jmlq/logger-plugin-fs
2
2
 
3
- Datasource de `sistema de archivos` para [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger).
4
- Escribe cada evento de log como **línea** (JSONL por defecto) y soporta `rotación por día` o por `tamaño`, manejo de `backpressure/drain`, `flush()` y `dispose()`.
5
-
6
- ---
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.
7
4
 
8
5
  ## 📦 Instalación
9
6
 
10
7
  ```bash
11
- # Con npm
12
- npm i @jmlq/logger @jmlq/logger-plugin-fs
13
-
14
- ```
15
-
16
- Este plugin **depende** de [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger). Asegúrate de instalar ambos paquetes.
17
-
18
- ## 🧱 Estructura del paquete
19
-
20
- ### 📝 Resumen rápido
21
-
22
- > - **`src/domain/`** — Reglas del negocio del plugin (sin dependencias de Node).
23
- > > - **`contracts/`**
24
- > > > - `clock.contract.ts` — Puerto que abstrae el tiempo actual (`now()`), permite testear rotación sin depender de `Date.now()`.
25
- > > > - `file-rotator.port.ts` — Puerto para rotación: calcula path esperado, tamaño, índice, etc.
26
- > > > - `stream-writer.port.ts` — Puerto para escritura secuencial: `write`, `once("drain")`, `end`.
27
- > > - **`value-objects/`**
28
- > > > - `file-name-pattern.vo.ts` — VO que encapsula el patrón de nombres (`{yyyy}{MM}{dd}`) con validación.
29
- > > > - `rotation-policy.vo.ts` — VO que define estrategia de rotación (`none | day | size`) con invariantes (`maxSizeMB > 0`).
30
- > > - **`types/`**
31
- > > > - `options.type.ts` — Define `FsDatasourceOptions` y `IFsSerializer` (contrato de serialización de líneas).
32
-
33
- > - **`src/application/`** — Casos de uso (orquestan, no dependen de Node).
34
- > > - **`dto/`**
35
- > > > - `save-log.dto.ts` — DTO de entrada: `{ log: ILog }`.
36
- > > - **`use-cases/`**
37
- > > > - `rotate-if-needed.usecase.ts` — Decide si se rota: compara fecha/tamaño contra `RotationPolicy`.
38
- > > > - `append-log.usecase.ts` — Serializa y escribe línea; maneja backpressure (`write=false` → espera `drain`).
39
- > > > - `persist-log.usecase.ts` — Orquesta: asegura writer, rota si corresponde, escribe, dispara hooks.
40
- > > - **`services/`**
41
- > > > - `fs-datasource.service.ts` — Implementa `ILogDatasource` del core usando los UC anteriores.
42
-
43
- > - **`src/infrastructure/`** — Adaptadores técnicos (Node.js).
44
- > > - **`fs/`**
45
- > > > - `fs-provider.ts` — Wrapper de `fs`/`fs.promises` (mockeable en tests).
46
- > > > - `path-utils.ts` — Utilidades para `join`, `splitBaseExt`, `formatPattern` (UTC).
47
- > > > - `file-rotator.adapter.ts` — Implementa `IFileRotatorPort` usando `fs-provider` y `path-utils`.
48
- > > > - `fs-writer.adapter.ts` — Implementa `IStreamWriterPort` con `fs.createWriteStream`.
49
- > > > - `node-clock.adapter.ts` — Implementa `IClock` devolviendo `new Date()`.
50
-
51
- > - **`src/presentation/`** — API pública (cara del paquete).
52
- > > - **`factory/`**
53
- > > > - `create-fs-datasource.ts` — Ensambla VO + adaptadores + casos de uso y devuelve un `ILogDatasource`.
54
- > > - `index.ts` — Barrel: reexporta la factory y VO/contratos públicos (`RotationPolicy`, `FileNamePattern`).
55
-
56
- #### [VER MAS](./ARQUITECTURA.md)
57
-
58
- ## 🧩 Configuración
59
-
60
- ### 🔐 Variables de Entorno (.env)
61
-
62
- ```ini
63
- # Ruta base (carpeta) para los archivos de log
64
- LOGGER_FS_PATH=./logs
65
-
66
- # Patrón (opcional). Si omites, usa "app-{yyyy}{MM}{dd}.log"
67
- LOGGER_FS_PATTERN=app-{yyyy}{MM}{dd}.log
68
-
69
- # Rotación: "day" | "size" | "none"
70
- LOGGER_FS_ROTATION=day
71
- LOGGER_FS_MAX_SIZE_MB=50
72
-
73
- # Logger core
74
- LOGGER_LEVEL=info # trace|debug|info|warn|error|fatal
75
-
8
+ npm install @jmlq/logger @jmlq/logger-plugin-fs
76
9
  ```
77
10
 
78
- ---
11
+ > **Nota:** Este plugin requiere [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger) como dependencia principal.
79
12
 
80
- ### 🚀 Uso del paquete
13
+ ## 🚀 Uso rápido
81
14
 
82
- ```tsx
83
- import { createLogger, LogLevel } from "@jmlq/logger";
15
+ ```typescript
16
+ import { LoggerFactory } from "@jmlq/logger";
84
17
  import { createFsDatasource } from "@jmlq/logger-plugin-fs";
85
18
 
86
- const ds = createFsDatasource({
87
- basePath: "./logs", // Carpeta destino
88
- mkdir: true, // Crea carpeta si no existe
89
- fileNamePattern: "app-{yyyy}{MM}{dd}.log", // Rotación diaria por fecha (UTC)
90
- // Alternativa por tamaño:
91
- // rotation: { by: "size", maxSizeMB: 50 }
19
+ // Crear datasource de filesystem
20
+ const fsDatasource = createFsDatasource({
21
+ basePath: "./logs",
22
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
92
23
  rotation: { by: "day" },
93
- // Serializador opcional (por defecto JSON.stringify)
94
- // serializer: { serialize: (log) => formatMyLine(log) },
95
- onRotate: (oldP, newP) => console.log("[fs] rotated:", oldP, "->", newP),
96
- onError: (e) => console.error("[fs] error:", e),
97
24
  });
98
25
 
99
- const logger = createLogger(ds, { minLevel: LogLevel.INFO });
26
+ // Crear logger usando la factory
27
+ const logger = LoggerFactory.create([fsDatasource]);
100
28
 
101
- logger.info("Servidor iniciado", { pid: process.pid });
29
+ // Usar el logger
30
+ logger.info("Aplicación iniciada", { timestamp: new Date(), pid: process.pid });
31
+ logger.error("Error de conexión", { service: "database", retries: 3 });
102
32
 
103
33
  // Cierre elegante
104
34
  process.on("SIGTERM", async () => {
105
- await logger.flush?.();
106
- await logger.dispose?.();
35
+ await logger.flush();
36
+ await logger.dispose();
107
37
  process.exit(0);
108
38
  });
109
39
  ```
110
40
 
111
- También:
41
+ ## ⚙️ Configuración del datasource
112
42
 
113
- ```ts
114
- import { createLogger, LogLevel } from "@jmlq/logger";
115
- import {
116
- createFsDatasource,
117
- RotationPolicy,
118
- FileNamePattern,
119
- } from "@jmlq/logger-plugin-fs";
43
+ El datasource de filesystem acepta las siguientes opciones de configuración:
120
44
 
121
- // Validación/invariantes tempranas con VO (throws si es inválido)
122
- const pt = new FileNamePattern("app-{yyyy}{MM}{dd}.log");
123
- const rt = new RotationPolicy("size", 50 /* maxSizeMB */);
45
+ ### Opciones principales
124
46
 
125
- const ds = createFsDatasource({
126
- basePath: "./logs",
127
- mkdir: true,
128
- // La factory acepta primitives; usamos los VO arriba solo para validar/centrar la decisión
129
- fileNamePattern: pt.pattern,
130
- rotation: { by: rt.by, maxSizeMB: rotation.maxSizeMB },
131
- });
47
+ | Opción | Tipo | Descripción | Por defecto |
48
+ | ----------------- | ---------------- | ------------------------------------------------------ | --------------------------- |
49
+ | `basePath` | `string` | Directorio base donde se guardarán los archivos de log | `"./logs"` |
50
+ | `fileNamePattern` | `string` | Patrón para nombrar archivos con tokens de fecha | `"app-{yyyy}{MM}{dd}.log"` |
51
+ | `rotation` | `RotationConfig` | Configuración de política de rotación | `{ by: "day" }` |
52
+ | `mkdir` | `boolean` | Crear directorio base si no existe | `true` |
53
+ | `serializer` | `LogSerializer` | Serializador personalizado para el formato de líneas | Serializer JSON por defecto |
54
+
55
+ ### Configuración de rotación
132
56
 
133
- const logger = createLogger(ds, { minLevel: LogLevel.INFO });
57
+ ```typescript
58
+ interface RotationConfig {
59
+ by: "none" | "day" | "size";
60
+ maxSizeMB?: number; // Para rotación por tamaño
61
+ maxFiles?: number; // Límite de archivos rotados
62
+ }
134
63
  ```
135
64
 
136
- #### ⚙️ Opciones soportadas
65
+ ### Patrones de nombre de archivo
137
66
 
138
- > - `basePath: string` carpeta donde se guardan los logs.
139
- > - `mkdir?: boolean` – crea la carpeta si no existe.
140
- > - `fileNamePattern?: string` – tokens {yyyy}{MM}{dd} (formateados en UTC).
141
- > > - Ejemplos: `"app-{yyyy}{MM}{dd}.log"`, `"service.log"`.
142
- > - `rotation?: { by: "none" | "day" | "size"; maxSizeMB?: number; maxFiles?: number }`
143
- > > - `day` → rota al cambiar la fecha (UTC).
144
- > > - `size` → rota al alcanzar `maxSizeMB` (genera app.1.log, app.2.log, …).
145
- > - `serializer?: { serialize(entry: unknown): string }` – una línea sin `\n`.
146
- > - `onRotate?: (oldPath, newPath) => void | Promise<void>`
147
- > - `onError?: (err) => void | Promise<void>`
67
+ Utiliza tokens que se reemplazan con valores de fecha (UTC):
148
68
 
149
- ---
69
+ - `{yyyy}` - Año completo (ej: 2024)
70
+ - `{MM}` - Mes con ceros (ej: 01, 12)
71
+ - `{dd}` - Día con ceros (ej: 01, 31)
150
72
 
151
- ### 🗂️ Formato de archivo
73
+ **Ejemplos:**
152
74
 
153
- > - **Una línea por evento** (JSONL por defecto).
154
- > - El `serializer` determina el formato de cada línea (sin salto).
155
- > - El plugin agrega `"\n"` y maneja `backpressure`/`drain` del stream.
75
+ - `"app-{yyyy}{MM}{dd}.log"` `app-20241201.log`
76
+ - `"service-{yyyy}-{MM}-{dd}.log"` `service-2024-12-01.log`
77
+ - `"application.log"` `application.log` (sin rotación por fecha)
156
78
 
157
- ---
79
+ ## 🔁 Políticas de rotación
158
80
 
159
- ### 🔁 Rotación
81
+ ### Rotación por fecha
160
82
 
161
- **Por día **(`rotation: { by: "day" }`)
83
+ Crea un archivo nuevo cada día basado en fecha UTC:
162
84
 
163
- > - El archivo activo se calcula con `fileNamePattern` usando la `fecha UTC actual`.
164
- > - Si al persistir cambia el día → `cierra` el stream anterior, abre uno nuevo y dispara `onRotate(old, new)`.
85
+ ```typescript
86
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
165
87
 
166
- **Por tamaño** (`rotation: { by: "size", maxSizeMB }`)
88
+ const datasource = createFsDatasource({
89
+ basePath: "./logs",
90
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
91
+ rotation: { by: "day" },
92
+ onRotate: (oldPath, newPath) => {
93
+ console.log(
94
+ `Log rotado: ${oldPath.absolutePath} → ${newPath.absolutePath}`
95
+ );
96
+ },
97
+ });
98
+ ```
167
99
 
168
- > - Comprueba el tamaño del archivo activo. Si `>= maxSizeMB` → rota a `app.1.log`, `app.2.log`, … (buscando el siguiente índice libre).
100
+ ### Rotación por tamaño
169
101
 
170
- ---
102
+ Rota cuando el archivo alcanza el tamaño máximo especificado:
171
103
 
172
- ### 🧯 Backpressure & drain (cómo evita perder logs)
104
+ ```typescript
105
+ const datasource = createFsDatasource({
106
+ basePath: "./logs",
107
+ fileNamePattern: "app.log",
108
+ rotation: {
109
+ by: "size",
110
+ maxSizeMB: 50, // Rota al alcanzar 50MB
111
+ maxFiles: 10, // Mantiene máximo 10 archivos rotados
112
+ },
113
+ onRotate: (oldPath, newPath) => {
114
+ console.log(
115
+ `Archivo rotado por tamaño: ${oldPath.absolutePath} → ${newPath.absolutePath}`
116
+ );
117
+ },
118
+ });
119
+ ```
173
120
 
174
- Node.js devuelve `false` en `stream.write()` cuando el **buffer interno está lleno** (backpressure).
175
- El plugin:
121
+ ### Sin rotación
176
122
 
177
- 1. Serializa el log + `"\n"`.
178
- 2. Llama `write(...)`.
179
- 3. Si devuelve `false`, **espera el evento** `drain` antes de continuar.
180
- 4. `flush()` espera a que se libere el buffer si `writableNeedDrain` es `true`.
181
- 5. `dispose()` cierra el stream actual drenando el buffer pendiente.
123
+ Mantiene un único archivo que crece indefinidamente:
182
124
 
183
- ---
125
+ ```typescript
126
+ const datasource = createFsDatasource({
127
+ basePath: "./logs",
128
+ fileNamePattern: "application.log",
129
+ rotation: { by: "none" },
130
+ });
131
+ ```
184
132
 
185
- ### 🌐 Zona horaria
133
+ ## 🧩 Ejemplo completo
186
134
 
187
- > - El formateo `{yyyy}{MM}{dd}` del patrón se realiza en **UTC** para evitar desfases por huso horario (máquinas/CI distintas).
188
- > - Si necesitas otro criterio (p.ej., “día de Guayaquil”), puedes:
189
- > > - Ajustar el **IClock** para proveer el “ahora” en otro huso y/o
190
- > > - Reemplazar el util de formateo si deseas `{…}` en local time.
135
+ ```typescript
136
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
137
+ import { LogLevel } from "@jmlq/logger";
191
138
 
192
- ---
139
+ const datasource = createFsDatasource({
140
+ basePath: "./logs",
141
+ fileNamePattern: "app-{yyyy}-{MM}-{dd}.log",
142
+ rotation: { by: "day" },
143
+ mkdir: true,
144
+ serializer: {
145
+ serialize(log: any) {
146
+ return JSON.stringify(log, null, 2);
147
+ },
148
+ },
149
+ onRotate: (oldPath, newPath) => {
150
+ console.log(`Rotación: ${oldPath.absolutePath} → ${newPath.absolutePath}`);
151
+ },
152
+ onError: (err) => {
153
+ console.error("Error en datasource:", err.message);
154
+ },
155
+ });
193
156
 
194
- ## FAQ
157
+ // Guardar logs
158
+ await datasource.save({
159
+ level: LogLevel.INFO,
160
+ message: "Servidor iniciado correctamente",
161
+ timestamp: Date.now(),
162
+ });
195
163
 
196
- **¿Puedo usar mi propio serializador?**
197
- Sí. Pasa serializer: { serialize: (log) => "mi-línea" }. Debe devolver una sola línea sin `\n`.
164
+ await datasource.save({
165
+ level: LogLevel.DEBUG,
166
+ message: "Conectando a base de datos...",
167
+ timestamp: Date.now(),
168
+ });
198
169
 
199
- **¿Qué pasa si el proceso se detiene en medio de un write?**
200
- Usamos los mecanismos de `Writable` (buffer + `drain`) y exponemos `flush()`/`dispose()` para el cierre. Llama a ambos en shutdown.
170
+ // Cierre
171
+ await datasource.flush();
172
+ await datasource.dispose();
173
+ ```
174
+
175
+ ## 🧪 Testing
176
+
177
+ ```typescript
178
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
179
+ import * as fs from "fs";
180
+ import * as path from "path";
181
+
182
+ describe("FileSystem Datasource", () => {
183
+ const testLogsPath = "./test-logs";
184
+
185
+ beforeEach(() => {
186
+ if (fs.existsSync(testLogsPath)) {
187
+ fs.rmSync(testLogsPath, { recursive: true });
188
+ }
189
+ });
190
+
191
+ test("debe crear archivo de log", async () => {
192
+ const datasource = createFsDatasource({
193
+ basePath: testLogsPath,
194
+ fileNamePattern: "test.log",
195
+ rotation: { by: "none" },
196
+ });
197
+
198
+ await datasource.save({
199
+ level: "info",
200
+ message: "Test message",
201
+ timestamp: new Date(),
202
+ });
203
+
204
+ await datasource.flush();
205
+
206
+ const logFile = path.join(testLogsPath, "test.log");
207
+ expect(fs.existsSync(logFile)).toBe(true);
208
+
209
+ const content = fs.readFileSync(logFile, "utf8");
210
+ expect(content).toContain("Test message");
211
+ });
212
+ });
213
+ ```
201
214
 
202
- **¿Puedo rotar por hora?**
203
- No out-of-the-box. Puedes implementar un **FileNamePattern** por hora (ej. `app-{yyyy}{MM}{dd}-{HH}.log`) + un `RotateIfNeededUseCase` extendido.
215
+ ## 📄 Más Información
204
216
 
205
- ---
217
+ - **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
218
+ - **[Guía de Instalación](./install.md)** - Configuración paso a paso
219
+ - **[Ejemplos](./examples/)** - Códigos de ejemplo funcionales
206
220
 
207
221
  ## 📄 Licencia
208
222