@jmlq/logger-plugin-fs 0.1.0-alpha.3 → 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 +137 -241
  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
@@ -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
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ // Value Object que expresa la política de rotación del adaptador FS.
3
+ // Esto es propio del dominio del plugin (infra-policy), no del core.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.RotationPolicy = void 0;
6
+ class RotationPolicy {
7
+ // by: modo de rotación
8
+ // maxSizeMB: umbral (sólo aplica cuando by === "size")
9
+ // maxFiles: retención opcional
10
+ constructor(by, maxSizeMB, maxFiles) {
11
+ this.by = by;
12
+ this.maxSizeMB = maxSizeMB;
13
+ this.maxFiles = maxFiles;
14
+ // Validación mínima de invariantes del VO
15
+ if (by === "size" && (!maxSizeMB || maxSizeMB <= 0)) {
16
+ throw new Error("[RotationPolicy] maxSizeMB > 0 es requerido cuando by = 'size'");
17
+ }
18
+ }
19
+ }
20
+ exports.RotationPolicy = RotationPolicy;
package/dist/index.d.ts CHANGED
@@ -1,13 +1,3 @@
1
- import { ILogDatasource, ILog } from "@jmlq/logger";
2
- type FsOpts = {
3
- filePath: string;
4
- };
5
- export declare class FileSystemDatasource implements ILogDatasource {
6
- private readonly opts;
7
- private stream;
8
- constructor(opts: FsOpts);
9
- save(log: ILog): Promise<void>;
10
- flush(): Promise<void>;
11
- dispose(): Promise<void>;
12
- }
13
- export {};
1
+ export { RotationPolicy, type RotationBy } from "./domain/value-objects";
2
+ export { createFsDatasource } from "./presentation/factory";
3
+ export { FileNamePattern } from "./domain/value-objects";
package/dist/index.js CHANGED
@@ -1,40 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.FileSystemDatasource = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- class FileSystemDatasource {
9
- constructor(opts) {
10
- this.opts = opts;
11
- // Abre una vez en modo append
12
- this.stream = node_fs_1.default.createWriteStream(this.opts.filePath, { flags: "a" });
13
- }
14
- async save(log) {
15
- if (!this.stream)
16
- throw new Error("FS stream closed");
17
- const line = JSON.stringify(log) + "\n";
18
- if (!this.stream.write(line)) {
19
- // backpressure: esperar a 'drain'
20
- await new Promise((resolve) => this.stream.once("drain", resolve));
21
- }
22
- }
23
- async flush() {
24
- if (!this.stream)
25
- return;
26
- if (!this.stream.writableNeedDrain)
27
- return;
28
- await new Promise((resolve) => this.stream.once("drain", resolve));
29
- }
30
- async dispose() {
31
- if (!this.stream)
32
- return;
33
- await new Promise((resolve, reject) => {
34
- this.stream.end(() => resolve()); // end -> cierra el fd
35
- this.stream.once("error", reject);
36
- });
37
- this.stream = null;
38
- }
39
- }
40
- exports.FileSystemDatasource = FileSystemDatasource;
3
+ exports.FileNamePattern = exports.createFsDatasource = exports.RotationPolicy = void 0;
4
+ var value_objects_1 = require("./domain/value-objects");
5
+ Object.defineProperty(exports, "RotationPolicy", { enumerable: true, get: function () { return value_objects_1.RotationPolicy; } });
6
+ var factory_1 = require("./presentation/factory");
7
+ Object.defineProperty(exports, "createFsDatasource", { enumerable: true, get: function () { return factory_1.createFsDatasource; } });
8
+ var value_objects_2 = require("./domain/value-objects");
9
+ Object.defineProperty(exports, "FileNamePattern", { enumerable: true, get: function () { return value_objects_2.FileNamePattern; } });
@@ -0,0 +1,17 @@
1
+ import type { ILog, ILogDatasource } from "@jmlq/logger";
2
+ import type { FsDatasourceOptions } from "../../domain/types";
3
+ export declare class FileSystemDatasource implements ILogDatasource {
4
+ private readonly opts;
5
+ private stream;
6
+ private readonly fs;
7
+ private readonly clock;
8
+ private readonly rotator;
9
+ private readonly rotateUC;
10
+ private readonly appendUC;
11
+ private readonly policy;
12
+ private readonly serializer;
13
+ constructor(opts: FsDatasourceOptions);
14
+ save(log: ILog): Promise<void>;
15
+ flush(): Promise<void>;
16
+ dispose(): Promise<void>;
17
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ // Datasource que implementa ILogDatasource del core (@jmlq/logger).
3
+ // Orquesta los casos de uso: rotar si hace falta y luego append.
4
+ // Aquí sí interactuamos con node:fs (a través del provider), streams y hooks.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FileSystemDatasource = void 0;
7
+ const fs_1 = require("../fs");
8
+ const use_cases_1 = require("../../application/use-cases");
9
+ const value_objects_1 = require("../../domain/value-objects");
10
+ class FileSystemDatasource {
11
+ constructor(opts) {
12
+ this.opts = opts;
13
+ this.stream = null; // Stream activo de escritura
14
+ this.clock = new fs_1.NodeClock(); // Reloj real (inyectable si se necesita)
15
+ // 1) Defaults seguros
16
+ this.fs = (0, fs_1.createFsProvider)();
17
+ this.policy = opts.rotation ?? new value_objects_1.RotationPolicy("day");
18
+ const pattern = opts.pattern ?? new value_objects_1.FileNamePattern("app-{yyyy}{MM}{dd}.log");
19
+ // Si el usuario no pasa serializer, usamos JSON.stringify como línea
20
+ this.serializer = opts.serializer ?? {
21
+ serialize: (x) => JSON.stringify(x),
22
+ };
23
+ // 2) Inicializar rotator (asegura carpeta si opts.mkdir === true)
24
+ this.rotator = new fs_1.FileRotator(this.fs, opts.basePath, this.clock, pattern,
25
+ // Para "size" podemos desear un nombre base fijo; si no, que use el daily pattern.
26
+ /* defaultSeedName */ undefined,
27
+ /* ensureDir */ !!opts.mkdir);
28
+ // 3) Preparar casos de uso (rotación + append)
29
+ this.rotateUC = new use_cases_1.RotateIfNeededUseCase(this.policy, this.rotator, this.clock);
30
+ this.appendUC = new use_cases_1.AppendLogUseCase(this.serializer);
31
+ // 4) Abrir el stream inicial en modo append
32
+ const initialPath = this.rotator.getActiveFilePath(); // path según patrón/fecha
33
+ this.stream = this.fs.createWriteStream(initialPath, { flags: "a" });
34
+ }
35
+ // Persiste un log. Orquesta:
36
+ // - Verificar si se requiere rotación (RotateIfNeededUseCase).
37
+ // - Si hay rotación: cierra stream actual, abre uno nuevo y dispara hook.
38
+ // - Escribir línea con backpressure (AppendLogUseCase).
39
+ async save(log) {
40
+ if (!this.stream)
41
+ throw new Error("[FileSystemDatasource] stream no inicializado");
42
+ try {
43
+ // 1) Rotación condicional
44
+ const nextPath = await this.rotateUC.execute();
45
+ if (nextPath) {
46
+ // Cerrar el stream actual de forma segura
47
+ await this.dispose();
48
+ // Actualizar activePath y abrir nuevo stream en append
49
+ this.rotator.setActivePath(nextPath);
50
+ this.stream = this.fs.createWriteStream(nextPath, { flags: "a" });
51
+ // Hook onRotate (no bloqueante)
52
+ Promise.resolve(this.opts.onRotate?.(/* old */ "", /* new */ nextPath)).catch((e) => this.opts.onError?.(e));
53
+ }
54
+ // 2) Append seguro con manejo de backpressure
55
+ await this.appendUC.execute({ log }, this.stream);
56
+ }
57
+ catch (err) {
58
+ // Notificar error (si hay handler) sin tumbar el proceso
59
+ await Promise.resolve(this.opts.onError?.(err));
60
+ // Re-lanzar si prefieres que el core lo capture:
61
+ // throw err;
62
+ }
63
+ }
64
+ // Espera a que el stream actual drene, si es necesario (útil antes de apagar).
65
+ async flush() {
66
+ if (!this.stream)
67
+ return;
68
+ const needDrain = this.stream.writableNeedDrain;
69
+ if (!needDrain)
70
+ return;
71
+ await new Promise((resolve) => this.stream.once("drain", resolve));
72
+ }
73
+ // Cierra el stream activo. Se usa al rotar y en shutdown.
74
+ async dispose() {
75
+ if (!this.stream)
76
+ return;
77
+ await new Promise((resolve, reject) => {
78
+ this.stream.end(() => resolve()); // end() drena y cierra el descriptor
79
+ this.stream.once("error", reject); // Por si ocurre un error al cerrar
80
+ });
81
+ this.stream = null;
82
+ }
83
+ }
84
+ exports.FileSystemDatasource = FileSystemDatasource;
@@ -0,0 +1 @@
1
+ export * from "./fs.datasource";
@@ -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"), exports);
@@ -0,0 +1,22 @@
1
+ import type { IFsProvider } from ".";
2
+ import type { IClock, IFileRotatorPort } from "../../domain/contracts";
3
+ import { FileNamePattern } from "../../domain/value-objects";
4
+ export declare class FileRotatorAdapter implements IFileRotatorPort {
5
+ private readonly fs;
6
+ private readonly basePath;
7
+ private readonly clock;
8
+ private readonly pattern;
9
+ private readonly seedName?;
10
+ private activePath;
11
+ constructor(fs: IFsProvider, // Acceso a fs
12
+ basePath: string, // Carpeta base de logs
13
+ clock: IClock, // Reloj (testable)
14
+ pattern: FileNamePattern, // Patrón de nombre
15
+ ensureDir: boolean, // Crear carpeta si no existe
16
+ seedName?: string | undefined);
17
+ getActiveFilePath(): string;
18
+ setActivePath(next: string): void;
19
+ expectedPathForToday(now: Date): string;
20
+ sizeOf(path: string): Promise<number | null>;
21
+ nextIndexedPath(): Promise<string>;
22
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileRotatorAdapter = void 0;
4
+ const _1 = require(".");
5
+ class FileRotatorAdapter {
6
+ constructor(fs, // Acceso a fs
7
+ basePath, // Carpeta base de logs
8
+ clock, // Reloj (testable)
9
+ pattern, // Patrón de nombre
10
+ ensureDir, // Crear carpeta si no existe
11
+ seedName // Nombre semilla para modo "size" (opcional)
12
+ ) {
13
+ this.fs = fs;
14
+ this.basePath = basePath;
15
+ this.clock = clock;
16
+ this.pattern = pattern;
17
+ this.seedName = seedName;
18
+ if (ensureDir)
19
+ this.fs.mkdirSync(this.basePath, { recursive: true });
20
+ // Arrancamos con el path esperado para "hoy"
21
+ this.activePath = this.expectedPathForToday(this.clock.now());
22
+ }
23
+ getActiveFilePath() {
24
+ return this.activePath;
25
+ }
26
+ setActivePath(next) {
27
+ this.activePath = next;
28
+ }
29
+ expectedPathForToday(now) {
30
+ const fname = (0, _1.formatPattern)(this.pattern.pattern, now);
31
+ return (0, _1.joinBase)(this.basePath, fname);
32
+ }
33
+ async sizeOf(path) {
34
+ const stat = await this.fs.statSafe(path);
35
+ return stat ? stat.size : null;
36
+ }
37
+ async nextIndexedPath() {
38
+ // Si hay seedName (ej. "app.log") úsalo; si no, usa pattern de hoy
39
+ const seed = this.seedName ?? (0, _1.formatPattern)(this.pattern.pattern, this.clock.now());
40
+ const { base, ext } = (0, _1.splitBaseExt)(seed);
41
+ let i = 1;
42
+ while (true) {
43
+ const candidate = (0, _1.joinBase)(this.basePath, `${base}.${i}${ext}`);
44
+ const exists = await this.fs.exists(candidate);
45
+ if (!exists)
46
+ return candidate;
47
+ i++;
48
+ }
49
+ }
50
+ }
51
+ exports.FileRotatorAdapter = FileRotatorAdapter;
@@ -0,0 +1,15 @@
1
+ import * as fs from "fs";
2
+ type CreateWriteStreamOpts = Parameters<typeof fs.createWriteStream>[1];
3
+ type WriteStreamType = ReturnType<typeof fs.createWriteStream>;
4
+ export interface IFsProvider {
5
+ createWriteStream(path: string, opts: CreateWriteStreamOpts): WriteStreamType;
6
+ mkdirSync(path: string, opts?: fs.MakeDirectoryOptions & {
7
+ recursive?: boolean;
8
+ }): void;
9
+ exists(path: string): Promise<boolean>;
10
+ statSafe(path: string): Promise<{
11
+ size: number;
12
+ } | null>;
13
+ }
14
+ export declare function createFsProvider(): IFsProvider;
15
+ export {};
@@ -0,0 +1,55 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createFsProvider = createFsProvider;
37
+ // Provider de fs compatible (tipos derivados para evitar errores por versión).
38
+ const fs = __importStar(require("fs"));
39
+ const fsp = __importStar(require("fs/promises"));
40
+ function createFsProvider() {
41
+ return {
42
+ createWriteStream: (p, opts) => fs.createWriteStream(p, opts),
43
+ mkdirSync: (p, opts) => fs.mkdirSync(p, { recursive: true, ...(opts ?? {}) }),
44
+ exists: async (p) => await new Promise((r) => fs.access(p, (e) => r(!e))),
45
+ statSafe: async (p) => {
46
+ try {
47
+ const s = await fsp.stat(p);
48
+ return { size: s.size };
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ },
54
+ };
55
+ }