@jmlq/logger-plugin-fs 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (60) hide show
  1. package/README.md +188 -0
  2. package/dist/application/dto/index.d.ts +1 -0
  3. package/dist/application/dto/index.js +17 -0
  4. package/dist/application/dto/save-log.dto.d.ts +4 -0
  5. package/dist/application/dto/save-log.dto.js +2 -0
  6. package/dist/application/services/fs-datasource.service.d.ts +9 -0
  7. package/dist/application/services/fs-datasource.service.js +22 -0
  8. package/dist/application/services/index.d.ts +1 -0
  9. package/dist/application/services/index.js +17 -0
  10. package/dist/application/use-cases/append-log.usecase.d.ts +7 -0
  11. package/dist/application/use-cases/append-log.usecase.js +19 -0
  12. package/dist/application/use-cases/index.d.ts +3 -0
  13. package/dist/application/use-cases/index.js +19 -0
  14. package/dist/application/use-cases/persist-log.usecase.d.ts +23 -0
  15. package/dist/application/use-cases/persist-log.usecase.js +74 -0
  16. package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +10 -0
  17. package/dist/application/use-cases/rotate-if-needed.usecase.js +39 -0
  18. package/dist/domain/contracts/clock.contract.d.ts +3 -0
  19. package/dist/domain/contracts/clock.contract.js +2 -0
  20. package/dist/domain/contracts/file-rotator.contract.d.ts +7 -0
  21. package/dist/domain/contracts/file-rotator.contract.js +2 -0
  22. package/dist/domain/contracts/index.d.ts +4 -0
  23. package/dist/domain/contracts/index.js +20 -0
  24. package/dist/domain/contracts/serializer.contract.d.ts +3 -0
  25. package/dist/domain/contracts/serializer.contract.js +2 -0
  26. package/dist/domain/contracts/stream-writer.port.d.ts +6 -0
  27. package/dist/domain/contracts/stream-writer.port.js +2 -0
  28. package/dist/domain/types/index.d.ts +1 -0
  29. package/dist/domain/types/index.js +17 -0
  30. package/dist/domain/types/options.type.d.ts +11 -0
  31. package/dist/domain/types/options.type.js +5 -0
  32. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +4 -0
  33. package/dist/domain/value-objects/file-name-pattern.vo.js +14 -0
  34. package/dist/domain/value-objects/index.d.ts +2 -0
  35. package/dist/domain/value-objects/index.js +18 -0
  36. package/dist/domain/value-objects/rotation-policy.vo.d.ts +7 -0
  37. package/dist/domain/value-objects/rotation-policy.vo.js +20 -0
  38. package/dist/index.d.ts +3 -13
  39. package/dist/index.js +7 -38
  40. package/dist/infrastructure/datasources/fs.datasource.d.ts +17 -0
  41. package/dist/infrastructure/datasources/fs.datasource.js +84 -0
  42. package/dist/infrastructure/datasources/index.d.ts +1 -0
  43. package/dist/infrastructure/datasources/index.js +17 -0
  44. package/dist/infrastructure/fs/file-rotator.adapter.d.ts +22 -0
  45. package/dist/infrastructure/fs/file-rotator.adapter.js +51 -0
  46. package/dist/infrastructure/fs/fs-provider.d.ts +15 -0
  47. package/dist/infrastructure/fs/fs-provider.js +55 -0
  48. package/dist/infrastructure/fs/fs-writer.adapter.d.ts +10 -0
  49. package/dist/infrastructure/fs/fs-writer.adapter.js +26 -0
  50. package/dist/infrastructure/fs/index.d.ts +5 -0
  51. package/dist/infrastructure/fs/index.js +21 -0
  52. package/dist/infrastructure/fs/node-clock.adapter.d.ts +4 -0
  53. package/dist/infrastructure/fs/node-clock.adapter.js +10 -0
  54. package/dist/infrastructure/fs/path-utils.d.ts +6 -0
  55. package/dist/infrastructure/fs/path-utils.js +26 -0
  56. package/dist/presentation/factory/create-fs-datasource.d.ts +15 -0
  57. package/dist/presentation/factory/create-fs-datasource.js +39 -0
  58. package/dist/presentation/factory/index.d.ts +1 -0
  59. package/dist/presentation/factory/index.js +17 -0
  60. package/package.json +15 -3
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # @jmlq/logger-plugin-fs
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
+ ---
7
+
8
+ ## 📦 Instalación
9
+
10
+ ```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
+ ---
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
+
76
+ ```
77
+
78
+ ---
79
+
80
+ ### 🚀 Uso del paquete
81
+
82
+ ```tsx
83
+ import { createLogger, LogLevel } from "@jmlq/logger";
84
+ import {
85
+ createFsDatasource,
86
+ RotationPolicy,
87
+ FileNamePattern,
88
+ } from "@jmlq/logger-plugin-fs";
89
+
90
+ const ds = createFsDatasource({
91
+ basePath: "./logs", // Carpeta destino
92
+ mkdir: true, // Crea carpeta si no existe
93
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log", // Rotación diaria por fecha (UTC)
94
+ // Alternativa por tamaño:
95
+ // rotation: { by: "size", maxSizeMB: 50 }
96
+ rotation: { by: "day" },
97
+ // Serializador opcional (por defecto JSON.stringify)
98
+ // serializer: { serialize: (log) => formatMyLine(log) },
99
+ onRotate: (oldP, newP) => console.log("[fs] rotated:", oldP, "->", newP),
100
+ onError: (e) => console.error("[fs] error:", e),
101
+ });
102
+
103
+ const logger = createLogger(ds, { minLevel: LogLevel.INFO });
104
+
105
+ logger.info("Servidor iniciado", { pid: process.pid });
106
+
107
+ // Cierre elegante
108
+ process.on("SIGTERM", async () => {
109
+ await logger.flush?.();
110
+ await logger.dispose?.();
111
+ process.exit(0);
112
+ });
113
+ ```
114
+
115
+ #### Opciones soportadas
116
+
117
+ > - `basePath: string` – carpeta donde se guardan los logs.
118
+ > - `mkdir?: boolean` – crea la carpeta si no existe.
119
+ > - `fileNamePattern?: string` – tokens {yyyy}{MM}{dd} (formateados en UTC).
120
+ > > - Ejemplos: `"app-{yyyy}{MM}{dd}.log"`, `"service.log"`.
121
+ > - `rotation?: { by: "none" | "day" | "size"; maxSizeMB?: number; maxFiles?: number }`
122
+ > > - `day` → rota al cambiar la fecha (UTC).
123
+ > > - `size` → rota al alcanzar `maxSizeMB` (genera app.1.log, app.2.log, …).
124
+ > - `serializer?: { serialize(entry: unknown): string }` – una línea sin `\n`.
125
+ > - `onRotate?: (oldPath, newPath) => void | Promise<void>`
126
+ > - `onError?: (err) => void | Promise<void>`
127
+
128
+ ---
129
+
130
+ ### 🗂️ Formato de archivo
131
+
132
+ > - **Una línea por evento** (JSONL por defecto).
133
+ > - El `serializer` determina el formato de cada línea (sin salto).
134
+ > - El plugin agrega `"\n"` y maneja `backpressure`/`drain` del stream.
135
+
136
+ ---
137
+
138
+ ### 🔁 Rotación
139
+
140
+ **Por día **(`rotation: { by: "day" }`)
141
+
142
+ > - El archivo activo se calcula con `fileNamePattern` usando la `fecha UTC actual`.
143
+ > - Si al persistir cambia el día → `cierra` el stream anterior, abre uno nuevo y dispara `onRotate(old, new)`.
144
+
145
+ **Por tamaño** (`rotation: { by: "size", maxSizeMB }`)
146
+
147
+ > - Comprueba el tamaño del archivo activo. Si `>= maxSizeMB` → rota a `app.1.log`, `app.2.log`, … (buscando el siguiente índice libre).
148
+
149
+ ---
150
+
151
+ ### 🧯 Backpressure & drain (cómo evita perder logs)
152
+
153
+ Node.js devuelve `false` en `stream.write()` cuando el **buffer interno está lleno** (backpressure).
154
+ El plugin:
155
+
156
+ 1. Serializa el log + `"\n"`.
157
+ 2. Llama `write(...)`.
158
+ 3. Si devuelve `false`, **espera el evento** `drain` antes de continuar.
159
+ 4. `flush()` espera a que se libere el buffer si `writableNeedDrain` es `true`.
160
+ 5. `dispose()` cierra el stream actual drenando el buffer pendiente.
161
+
162
+ ---
163
+
164
+ ### 🌐 Zona horaria
165
+
166
+ > - El formateo `{yyyy}{MM}{dd}` del patrón se realiza en **UTC** para evitar desfases por huso horario (máquinas/CI distintas).
167
+ > - Si necesitas otro criterio (p.ej., “día de Guayaquil”), puedes:
168
+ > > - Ajustar el **IClock** para proveer el “ahora” en otro huso y/o
169
+ > > - Reemplazar el util de formateo si deseas `{…}` en local time.
170
+
171
+ ---
172
+
173
+ ## ❓ FAQ
174
+
175
+ **¿Puedo usar mi propio serializador?**
176
+ Sí. Pasa serializer: { serialize: (log) => "mi-línea" }. Debe devolver una sola línea sin `\n`.
177
+
178
+ **¿Qué pasa si el proceso se detiene en medio de un write?**
179
+ Usamos los mecanismos de `Writable` (buffer + `drain`) y exponemos `flush()`/`dispose()` para el cierre. Llama a ambos en shutdown.
180
+
181
+ **¿Puedo rotar por hora?**
182
+ No out-of-the-box. Puedes implementar un **FileNamePattern** por hora (ej. `app-{yyyy}{MM}{dd}-{HH}.log`) + un `RotateIfNeededUseCase` extendido.
183
+
184
+ ---
185
+
186
+ ## 📄 Licencia
187
+
188
+ MIT © Mauricio Lahuasi
@@ -0,0 +1 @@
1
+ export * from "./save-log.dto";
@@ -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("./save-log.dto"), exports);
@@ -0,0 +1,4 @@
1
+ import type { ILog } from "@jmlq/logger";
2
+ export interface SaveLogDTO {
3
+ log: ILog;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { ILogDatasource, ILog } from "@jmlq/logger";
2
+ import { PersistLogUseCase } from "../use-cases";
3
+ export declare class FsDatasourceService implements ILogDatasource {
4
+ private readonly persistUC;
5
+ constructor(persistUC: PersistLogUseCase);
6
+ save(log: ILog): Promise<void>;
7
+ flush(): Promise<void>;
8
+ dispose(): Promise<void>;
9
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FsDatasourceService = void 0;
4
+ class FsDatasourceService {
5
+ constructor(persistUC) {
6
+ this.persistUC = persistUC;
7
+ }
8
+ // Guarda un log (core llama esto). Orquesta vía PersistLogUseCase.
9
+ async save(log) {
10
+ const dto = { log };
11
+ await this.persistUC.execute(dto);
12
+ }
13
+ // Espera drenaje del buffer (útil para shutdown)
14
+ async flush() {
15
+ await this.persistUC.flush();
16
+ }
17
+ // Cierra recursos (stream)
18
+ async dispose() {
19
+ await this.persistUC.dispose();
20
+ }
21
+ }
22
+ exports.FsDatasourceService = FsDatasourceService;
@@ -0,0 +1 @@
1
+ export * from "./fs-datasource.service";
@@ -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("./fs-datasource.service"), exports);
@@ -0,0 +1,7 @@
1
+ import { IFsSerializer, IStreamWriterPort } from "../../domain/contracts";
2
+ import { SaveLogDTO } from "../dto";
3
+ export declare class AppendLogUseCase {
4
+ private readonly serializer;
5
+ constructor(serializer: IFsSerializer);
6
+ execute(input: SaveLogDTO, writer: IStreamWriterPort): Promise<void>;
7
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ // Serializa (línea + '\n') y escribe en el stream con manejo de backpressure.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AppendLogUseCase = void 0;
5
+ class AppendLogUseCase {
6
+ constructor(serializer) {
7
+ this.serializer = serializer;
8
+ }
9
+ async execute(input, writer) {
10
+ // 1) Serializa el log a texto + salto de línea
11
+ const line = this.serializer.serialize(input.log) + "\n";
12
+ // 2) Intenta escribir; si devuelve false, espera 'drain'
13
+ const ok = writer.write(line);
14
+ if (!ok) {
15
+ await new Promise((resolve) => writer.once("drain", resolve));
16
+ }
17
+ }
18
+ }
19
+ exports.AppendLogUseCase = AppendLogUseCase;
@@ -0,0 +1,3 @@
1
+ export * from "./rotate-if-needed.usecase";
2
+ export * from "./append-log.usecase";
3
+ export * from "./persist-log.usecase";
@@ -0,0 +1,19 @@
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.usecase"), exports);
18
+ __exportStar(require("./append-log.usecase"), exports);
19
+ __exportStar(require("./persist-log.usecase"), exports);
@@ -0,0 +1,23 @@
1
+ import { AppendLogUseCase, RotateIfNeededUseCase } from ".";
2
+ import { IFileRotatorPort, IStreamWriterPort } from "../../domain/contracts";
3
+ import { SaveLogDTO } from "../dto";
4
+ export declare class PersistLogUseCase {
5
+ private readonly rotateUC;
6
+ private readonly appendUC;
7
+ private readonly rotator;
8
+ private readonly openWriter;
9
+ private readonly onRotate?;
10
+ private readonly onError?;
11
+ constructor(rotateUC: RotateIfNeededUseCase, // caso de uso de rotación
12
+ appendUC: AppendLogUseCase, // caso de uso de escritura
13
+ rotator: IFileRotatorPort, // puerto para actualizar activePath
14
+ openWriter: (path: string) => IStreamWriterPort, // factory: abre stream
15
+ onRotate?: ((oldPath: string, newPath: string) => void | Promise<void>) | undefined, onError?: ((err: unknown) => void | Promise<void>) | undefined);
16
+ private writer;
17
+ private path;
18
+ private ensureWriter;
19
+ private disposeWriter;
20
+ execute(input: SaveLogDTO): Promise<void>;
21
+ flush(): Promise<void>;
22
+ dispose(): Promise<void>;
23
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ // Orquesta: (1) rotar si hace falta, (2) escribir línea, (3) hooks de rotación/errores.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.PersistLogUseCase = void 0;
5
+ class PersistLogUseCase {
6
+ constructor(rotateUC, // caso de uso de rotación
7
+ appendUC, // caso de uso de escritura
8
+ rotator, // puerto para actualizar activePath
9
+ openWriter, // factory: abre stream
10
+ onRotate, onError) {
11
+ this.rotateUC = rotateUC;
12
+ this.appendUC = appendUC;
13
+ this.rotator = rotator;
14
+ this.openWriter = openWriter;
15
+ this.onRotate = onRotate;
16
+ this.onError = onError;
17
+ this.writer = null; // writer activo
18
+ this.path = null; // path activo
19
+ }
20
+ // Inicializa el writer si no existe (idempotente)
21
+ ensureWriter(currentPath) {
22
+ if (!this.writer) {
23
+ this.writer = this.openWriter(currentPath);
24
+ this.path = currentPath;
25
+ }
26
+ }
27
+ // Cierra writer activo de forma segura
28
+ async disposeWriter() {
29
+ if (!this.writer)
30
+ return;
31
+ await new Promise((resolve) => this.writer.end(resolve));
32
+ this.writer = null;
33
+ }
34
+ // Persistir un log con rotación condicional
35
+ async execute(input) {
36
+ try {
37
+ // 1) Asegurar writer inicial para el path actual del rotator
38
+ const currentPath = this.rotator.getActiveFilePath();
39
+ this.ensureWriter(currentPath);
40
+ // 2) Consultar si debemos rotar → abrir nuevo writer si hace falta
41
+ const nextPath = await this.rotateUC.execute();
42
+ if (nextPath) {
43
+ const old = this.path;
44
+ await this.disposeWriter();
45
+ this.rotator.setActivePath(nextPath);
46
+ this.writer = this.openWriter(nextPath);
47
+ this.path = nextPath;
48
+ // Notificar rotación (no bloqueante)
49
+ Promise.resolve(this.onRotate?.(old, nextPath)).catch((e) => this.onError?.(e));
50
+ }
51
+ // 3) Escribir línea manejando backpressure
52
+ await this.appendUC.execute(input, this.writer);
53
+ }
54
+ catch (err) {
55
+ // Notificar error sin tumbar el proceso (política del plugin)
56
+ await Promise.resolve(this.onError?.(err));
57
+ // Si quieres propagar al core, descomenta:
58
+ // throw err;
59
+ }
60
+ }
61
+ // Esperar drenaje del buffer
62
+ async flush() {
63
+ if (!this.writer)
64
+ return;
65
+ if (this.writer.writableNeedDrain) {
66
+ await new Promise((resolve) => this.writer.once("drain", resolve));
67
+ }
68
+ }
69
+ // Cerrar el writer activo
70
+ async dispose() {
71
+ await this.disposeWriter();
72
+ }
73
+ }
74
+ exports.PersistLogUseCase = PersistLogUseCase;
@@ -0,0 +1,10 @@
1
+ import { IClock, IFileRotatorPort } from "../../domain/contracts";
2
+ import { RotationPolicy } from "../../domain/value-objects";
3
+ export declare class RotateIfNeededUseCase {
4
+ private readonly policy;
5
+ private readonly rotator;
6
+ private readonly clock;
7
+ private readonly maxSizeBytes?;
8
+ constructor(policy: RotationPolicy, rotator: IFileRotatorPort, clock: IClock, maxSizeBytes?: number | undefined);
9
+ execute(): Promise<string | null>;
10
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ // Caso de uso: evaluar si corresponde rotar el archivo y, si corresponde, devolver el nuevo path.
3
+ // NO toca node:fs directamente; delega a servicios de infraestructura (rotator).
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.RotateIfNeededUseCase = void 0;
6
+ class RotateIfNeededUseCase {
7
+ // Dependencias: política (VO), puerto de rotación y reloj.
8
+ constructor(policy, rotator, clock, maxSizeBytes // cache de bytes (derivado de policy.maxSizeMB)
9
+ ) {
10
+ this.policy = policy;
11
+ this.rotator = rotator;
12
+ this.clock = clock;
13
+ this.maxSizeBytes = maxSizeBytes;
14
+ }
15
+ // Devuelve null si no se requiere rotar; o el "nextPath" a usar si hay rotación.
16
+ async execute() {
17
+ // Selecciona estrategia según la política
18
+ if (this.policy.by === "none")
19
+ return null;
20
+ if (this.policy.by === "day") {
21
+ // Compara path actual vs path esperado para "hoy"
22
+ const now = this.clock.now();
23
+ const expected = this.rotator.expectedPathForToday(now);
24
+ return expected !== this.rotator.getActiveFilePath() ? expected : null;
25
+ }
26
+ if (this.policy.by === "size") {
27
+ // Si el archivo supera el umbral, pedimos un nuevo path indexado
28
+ const current = this.rotator.getActiveFilePath();
29
+ const size = await this.rotator.sizeOf(current);
30
+ const limit = this.maxSizeBytes ?? this.policy.maxSizeMB * 1024 * 1024;
31
+ if ((size ?? 0) >= limit) {
32
+ return await this.rotator.nextIndexedPath();
33
+ }
34
+ return null;
35
+ }
36
+ return null;
37
+ }
38
+ }
39
+ exports.RotateIfNeededUseCase = RotateIfNeededUseCase;
@@ -0,0 +1,3 @@
1
+ export interface IClock {
2
+ now(): Date;
3
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export interface IFileRotatorPort {
2
+ getActiveFilePath(): string;
3
+ expectedPathForToday(now: Date): string;
4
+ sizeOf(path: string): Promise<number | null>;
5
+ nextIndexedPath(): Promise<string>;
6
+ setActivePath(next: string): void;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export * from "./clock.contract";
2
+ export * from "./serializer.contract";
3
+ export * from "./file-rotator.contract";
4
+ export * from "./stream-writer.port";
@@ -0,0 +1,20 @@
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("./clock.contract"), exports);
18
+ __exportStar(require("./serializer.contract"), exports);
19
+ __exportStar(require("./file-rotator.contract"), exports);
20
+ __exportStar(require("./stream-writer.port"), exports);
@@ -0,0 +1,3 @@
1
+ export interface IFsSerializer {
2
+ serialize(entry: unknown): string;
3
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export interface IStreamWriterPort {
2
+ write(line: string): boolean;
3
+ once(event: "drain" | "error", cb: (...a: any[]) => void): void;
4
+ end(cb: () => void): void;
5
+ readonly writableNeedDrain?: boolean;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export * from "./options.type";
@@ -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("./options.type"), exports);
@@ -0,0 +1,11 @@
1
+ import { IFsSerializer } from "../contracts";
2
+ import { FileNamePattern, RotationPolicy } from "../value-objects";
3
+ export interface FsDatasourceOptions {
4
+ basePath: string;
5
+ mkdir?: boolean;
6
+ pattern?: FileNamePattern;
7
+ rotation?: RotationPolicy;
8
+ serializer?: IFsSerializer;
9
+ onRotate?: (oldPath: string, newPath: string) => void | Promise<void>;
10
+ onError?: (err: unknown) => void | Promise<void>;
11
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // Tipo de opciones de alto nivel del plugin, expuesto a la "presentación" (factory).
3
+ // Reúne los contratos/VOs de dominio, pero sin detalles de node:fs.
4
+ // Esto mantiene la API pública limpia y testeable.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export declare class FileNamePattern {
2
+ readonly pattern: string;
3
+ constructor(pattern: string);
4
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileNamePattern = void 0;
4
+ // Value Object para el patrón de nombre del archivo.
5
+ // Soporta tokens {yyyy}, {MM}, {dd}; el formateo real ocurre en infraestructura.
6
+ class FileNamePattern {
7
+ constructor(pattern) {
8
+ this.pattern = pattern;
9
+ if (!pattern || typeof pattern !== "string") {
10
+ throw new Error("[FileNamePattern] pattern inválido");
11
+ }
12
+ }
13
+ }
14
+ exports.FileNamePattern = FileNamePattern;
@@ -0,0 +1,2 @@
1
+ export * from "./file-name-pattern.vo";
2
+ export * from "./rotation-policy.vo";
@@ -0,0 +1,18 @@
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("./file-name-pattern.vo"), exports);
18
+ __exportStar(require("./rotation-policy.vo"), exports);
@@ -0,0 +1,7 @@
1
+ export type RotationBy = "none" | "day" | "size";
2
+ export declare class RotationPolicy {
3
+ readonly by: RotationBy;
4
+ readonly maxSizeMB?: number | undefined;
5
+ readonly maxFiles?: number | undefined;
6
+ constructor(by: RotationBy, maxSizeMB?: number | undefined, maxFiles?: number | undefined);
7
+ }