@jmlq/logger-plugin-fs 0.1.0-alpha.1 → 0.1.0-alpha.11
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.
- package/README.md +551 -0
- package/architecture.md +299 -0
- package/dist/application/dto/index.d.ts +1 -0
- package/dist/application/dto/index.js +17 -0
- package/dist/application/dto/rotate-if-needed.request.d.ts +10 -0
- package/dist/application/dto/rotate-if-needed.request.js +2 -0
- package/dist/application/factory/create-fs-datasource.factory.d.ts +16 -0
- package/dist/application/factory/create-fs-datasource.factory.js +51 -0
- package/dist/application/factory/index.d.ts +1 -0
- package/dist/application/factory/index.js +17 -0
- package/dist/application/types/filesystem-datasource-options.type.d.ts +45 -0
- package/dist/application/types/filesystem-datasource-options.type.js +2 -0
- package/dist/application/types/index.d.ts +1 -0
- package/dist/application/types/index.js +17 -0
- package/dist/application/use-cases/append-log.use-case.d.ts +30 -0
- package/dist/application/use-cases/append-log.use-case.js +39 -0
- package/dist/application/use-cases/ensure-directory.use-case.d.ts +58 -0
- package/dist/application/use-cases/ensure-directory.use-case.js +64 -0
- package/dist/application/use-cases/find-logs-use-case.d.ts +17 -0
- package/dist/application/use-cases/find-logs-use-case.js +64 -0
- package/dist/application/use-cases/index.d.ts +5 -0
- package/dist/application/use-cases/index.js +21 -0
- package/dist/application/use-cases/persist-log.use-case.d.ts +96 -0
- package/dist/application/use-cases/persist-log.use-case.js +105 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +55 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.js +61 -0
- package/dist/domain/model/index.d.ts +1 -0
- package/dist/domain/model/index.js +17 -0
- package/dist/domain/model/log-entry.model.d.ts +8 -0
- package/dist/domain/model/log-entry.model.js +2 -0
- package/dist/domain/ports/file/file-path.port.d.ts +38 -0
- package/dist/domain/ports/file/file-path.port.js +2 -0
- package/dist/domain/ports/file/file-rotator.port.d.ts +42 -0
- package/dist/domain/ports/file/file-rotator.port.js +2 -0
- package/dist/domain/ports/file/index.d.ts +2 -0
- package/dist/domain/ports/file/index.js +18 -0
- package/dist/domain/ports/file/log-stream-writer.port.d.ts +70 -0
- package/dist/domain/ports/file/log-stream-writer.port.js +2 -0
- package/dist/domain/ports/filesystem-provider.port.d.ts +61 -0
- package/dist/domain/ports/filesystem-provider.port.js +2 -0
- package/dist/domain/ports/index.d.ts +5 -0
- package/dist/domain/ports/index.js +21 -0
- package/dist/domain/ports/logs/find/index.d.ts +2 -0
- package/dist/domain/ports/logs/find/index.js +18 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.js +2 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.js +2 -0
- package/dist/domain/ports/logs/index.d.ts +2 -0
- package/dist/domain/ports/logs/index.js +18 -0
- package/dist/domain/ports/logs/log-datasource.port.d.ts +10 -0
- package/dist/domain/ports/logs/log-datasource.port.js +2 -0
- package/dist/domain/ports/system-clock.port.d.ts +8 -0
- package/dist/domain/ports/system-clock.port.js +2 -0
- package/dist/domain/request/index.d.ts +2 -0
- package/dist/domain/request/index.js +18 -0
- package/dist/domain/request/log-filter.request.d.ts +9 -0
- package/dist/domain/request/log-filter.request.js +2 -0
- package/dist/domain/request/save-log.request.d.ts +7 -0
- package/dist/domain/request/save-log.request.js +2 -0
- package/dist/domain/response/index.d.ts +1 -0
- package/dist/domain/response/index.js +17 -0
- package/dist/domain/response/log.response.d.ts +8 -0
- package/dist/domain/response/log.response.js +2 -0
- package/dist/domain/types/fs-rotation-by.type.d.ts +8 -0
- package/dist/domain/types/fs-rotation-by.type.js +2 -0
- package/dist/domain/types/index.d.ts +1 -0
- package/dist/domain/types/index.js +17 -0
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +36 -0
- package/dist/domain/value-objects/file-name-pattern.vo.js +53 -0
- package/dist/domain/value-objects/file-path.vo.d.ts +91 -0
- package/dist/domain/value-objects/file-path.vo.js +100 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.d.ts +51 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.js +76 -0
- package/dist/domain/value-objects/file-size.vo.d.ts +75 -0
- package/dist/domain/value-objects/file-size.vo.js +114 -0
- package/dist/domain/value-objects/index.d.ts +5 -0
- package/dist/domain/value-objects/index.js +21 -0
- package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
- package/dist/domain/value-objects/log-level.vo.js +13 -0
- package/dist/index.d.ts +4 -13
- package/dist/index.js +10 -38
- package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +79 -0
- package/dist/infrastructure/adapters/file-rotator.adapter.js +171 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.d.ts +26 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.js +45 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.d.ts +6 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.js +54 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.d.ts +4 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.js +53 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.d.ts +122 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.js +182 -0
- package/dist/infrastructure/adapters/index.d.ts +8 -0
- package/dist/infrastructure/adapters/index.js +24 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.d.ts +80 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.js +163 -0
- package/dist/infrastructure/adapters/system-clock.adapter.d.ts +25 -0
- package/dist/infrastructure/adapters/system-clock.adapter.js +30 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.d.ts +47 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.js +141 -0
- package/dist/infrastructure/errors/file-operation.error.d.ts +28 -0
- package/dist/infrastructure/errors/file-operation.error.js +54 -0
- package/dist/infrastructure/errors/index.d.ts +1 -0
- package/dist/infrastructure/errors/index.js +17 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.d.ts +8 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.js +2 -0
- package/dist/infrastructure/errors/types/file-operation.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/file-operation.type.js +2 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.js +2 -0
- package/dist/infrastructure/errors/types/index.d.ts +3 -0
- package/dist/infrastructure/errors/types/index.js +19 -0
- package/dist/infrastructure/filesystem/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/index.js +17 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +12 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
- package/dist/infrastructure/filesystem/types/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/types/index.js +17 -0
- package/package.json +40 -13
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnsureDirectoryUseCase = void 0;
|
|
4
|
+
const errors_1 = require("../../infrastructure/errors");
|
|
5
|
+
/**
|
|
6
|
+
* EnsureDirectoryUseCase
|
|
7
|
+
* -----------------------------------------------------------------------------
|
|
8
|
+
* Caso de uso de Application que garantiza que exista un directorio.
|
|
9
|
+
*
|
|
10
|
+
* Clean Architecture:
|
|
11
|
+
* - Depende de un port de dominio (IFileSystemProviderPort) para IO.
|
|
12
|
+
* - No depende de infraestructura directamente.
|
|
13
|
+
*
|
|
14
|
+
* Comportamiento:
|
|
15
|
+
* - Si createIfNotExists es false, no hace nada (no side-effects).
|
|
16
|
+
* - Si el directorio no existe, lo crea (recursive).
|
|
17
|
+
*/
|
|
18
|
+
class EnsureDirectoryUseCase {
|
|
19
|
+
constructor(
|
|
20
|
+
/**
|
|
21
|
+
* Port de filesystem
|
|
22
|
+
*/
|
|
23
|
+
fileSystemProvider,
|
|
24
|
+
/**
|
|
25
|
+
* Ruta del directorio base a asegurar.
|
|
26
|
+
* Nota: aquí se usa string; idealmente podría ser un VO (FilePath/DirectoryPath)
|
|
27
|
+
* si tu dominio ya lo maneja.
|
|
28
|
+
*/
|
|
29
|
+
basePath,
|
|
30
|
+
/**
|
|
31
|
+
* Flag de configuración: si es false, el caso de uso es no-op.
|
|
32
|
+
*/
|
|
33
|
+
createIfNotExists = true) {
|
|
34
|
+
this.fileSystemProvider = fileSystemProvider;
|
|
35
|
+
this.basePath = basePath;
|
|
36
|
+
this.createIfNotExists = createIfNotExists;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ejecuta la operación:
|
|
40
|
+
* - verifica existencia
|
|
41
|
+
* - crea el directorio si no existe
|
|
42
|
+
* - encapsula errores en un error de dominio
|
|
43
|
+
*/
|
|
44
|
+
async execute() {
|
|
45
|
+
// Si la config indica no crear, salimos sin hacer nada.
|
|
46
|
+
if (!this.createIfNotExists) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
// El basePath ES el directorio que queremos asegurar.
|
|
51
|
+
const exists = await this.fileSystemProvider.exists(this.basePath);
|
|
52
|
+
if (!exists) {
|
|
53
|
+
await this.fileSystemProvider.mkdir(this.basePath, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Error de dominio (no infraestructura) para mantener la dirección de dependencias.
|
|
60
|
+
throw errors_1.FileOperationError.create("mkdir", this.basePath, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.EnsureDirectoryUseCase = EnsureDirectoryUseCase;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LogFilterRequest } from "../../domain/request";
|
|
2
|
+
import { ILogFileEnumeratorPort, ILogFileLineReaderPort } from "../../domain/ports";
|
|
3
|
+
import { ILogResponse } from "../../domain/response";
|
|
4
|
+
export interface IFindLogsUseCase {
|
|
5
|
+
execute(filter?: LogFilterRequest): Promise<ILogResponse[]>;
|
|
6
|
+
}
|
|
7
|
+
export declare class FindLogsUseCase implements IFindLogsUseCase {
|
|
8
|
+
private readonly deps;
|
|
9
|
+
constructor(deps: {
|
|
10
|
+
fileEnumerator: ILogFileEnumeratorPort;
|
|
11
|
+
lineReader: ILogFileLineReaderPort;
|
|
12
|
+
});
|
|
13
|
+
execute(filter?: LogFilterRequest): Promise<ILogResponse[]>;
|
|
14
|
+
private matchesFilter;
|
|
15
|
+
private applyPagination;
|
|
16
|
+
private messageToText;
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FindLogsUseCase = void 0;
|
|
4
|
+
class FindLogsUseCase {
|
|
5
|
+
constructor(deps) {
|
|
6
|
+
this.deps = deps;
|
|
7
|
+
}
|
|
8
|
+
async execute(filter) {
|
|
9
|
+
// if (!this.ds.find) return [];
|
|
10
|
+
const files = await this.deps.fileEnumerator.listLogFiles();
|
|
11
|
+
const records = [];
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
for await (const line of this.deps.lineReader.readLines(file)) {
|
|
14
|
+
if (!line.trim())
|
|
15
|
+
continue;
|
|
16
|
+
let record;
|
|
17
|
+
try {
|
|
18
|
+
record = JSON.parse(line);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
continue; // línea corrupta → la ignoras
|
|
22
|
+
}
|
|
23
|
+
if (!this.matchesFilter(record, filter))
|
|
24
|
+
continue;
|
|
25
|
+
records.push(record);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return this.applyPagination(records, filter);
|
|
29
|
+
}
|
|
30
|
+
matchesFilter(r, f) {
|
|
31
|
+
if (f?.levelMin != null && r.level < f.levelMin)
|
|
32
|
+
return false;
|
|
33
|
+
if (f?.since != null && r.timestamp < f.since)
|
|
34
|
+
return false;
|
|
35
|
+
if (f?.until != null && r.timestamp > f.until)
|
|
36
|
+
return false;
|
|
37
|
+
if (f?.query?.trim()) {
|
|
38
|
+
const q = f.query.trim().toLowerCase();
|
|
39
|
+
const msgText = this.messageToText(r.message).toLowerCase();
|
|
40
|
+
if (!msgText.includes(q))
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
applyPagination(records, f) {
|
|
46
|
+
const ordered = records.slice().sort((a, b) => a.timestamp - b.timestamp);
|
|
47
|
+
const limit = typeof f?.limit === "number" ? f.limit : ordered.length;
|
|
48
|
+
const page = typeof f?.offset === "number" ? f.offset : 0;
|
|
49
|
+
const slice = ordered.slice(page * limit, page * limit + limit);
|
|
50
|
+
return slice.reverse();
|
|
51
|
+
}
|
|
52
|
+
messageToText(message) {
|
|
53
|
+
if (typeof message === "string")
|
|
54
|
+
return message;
|
|
55
|
+
// message es objeto: lo convertimos a string para búsqueda simple
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(message);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.FindLogsUseCase = FindLogsUseCase;
|
|
@@ -0,0 +1,21 @@
|
|
|
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("./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);
|
|
21
|
+
__exportStar(require("./find-logs-use-case"), exports);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ISystemClockPort, ILogStreamWriterPort, IFileRotatorPort } from "../../domain/ports";
|
|
2
|
+
import { SaveLogRequest } from "../../domain/request";
|
|
3
|
+
import { IAppendLogUseCase, IEnsureDirectoryUseCase, IRotateIfNeededUseCase } from ".";
|
|
4
|
+
import { FileRotationPolicy } from "../../domain/value-objects";
|
|
5
|
+
/**
|
|
6
|
+
* Contrato del caso de uso principal para persistir logs.
|
|
7
|
+
*/
|
|
8
|
+
export interface IPersistLogUseCase {
|
|
9
|
+
execute(dto: SaveLogRequest): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* PersistLogUseCase
|
|
13
|
+
* -----------------------------------------------------------------------------
|
|
14
|
+
* Caso de uso de Application que orquesta el flujo completo:
|
|
15
|
+
* - asegura el directorio base
|
|
16
|
+
* - decide si rota según política y fecha actual
|
|
17
|
+
* - garantiza que el stream está abierto en el archivo correcto
|
|
18
|
+
* - escribe el log (append)
|
|
19
|
+
*
|
|
20
|
+
* Nota (Clean Architecture):
|
|
21
|
+
* - No hace IO directamente: todo IO va por ports/use-cases delegados.
|
|
22
|
+
* - Centraliza la secuencia y manejo de errores del flujo.
|
|
23
|
+
*/
|
|
24
|
+
export declare class PersistLogUseCase implements IPersistLogUseCase {
|
|
25
|
+
/**
|
|
26
|
+
* Port para obtener la hora actual
|
|
27
|
+
*/
|
|
28
|
+
private readonly systemClock;
|
|
29
|
+
/**
|
|
30
|
+
* Port relacionado con rotación/naming (según diseño actual).
|
|
31
|
+
* Idealmente: la parte de naming debería estar separada en un "namer".
|
|
32
|
+
*/
|
|
33
|
+
private readonly fileRotator;
|
|
34
|
+
/**
|
|
35
|
+
* Writer por stream
|
|
36
|
+
*/
|
|
37
|
+
private readonly streamWriter;
|
|
38
|
+
/**
|
|
39
|
+
* Política de rotación (VO de dominio).
|
|
40
|
+
*/
|
|
41
|
+
private readonly rotationPolicy;
|
|
42
|
+
/**
|
|
43
|
+
* Use-case que ejecuta rotación si corresponde
|
|
44
|
+
*/
|
|
45
|
+
private readonly rotateIfNeededUseCase;
|
|
46
|
+
/**
|
|
47
|
+
* Use-case que serializa y escribe el log al stream.
|
|
48
|
+
*/
|
|
49
|
+
private readonly appendLogUseCase;
|
|
50
|
+
/**
|
|
51
|
+
* Use-case que asegura que el directorio base exista.
|
|
52
|
+
*/
|
|
53
|
+
private readonly ensureDirectoryUseCase;
|
|
54
|
+
/**
|
|
55
|
+
* Hook opcional para reportar/registrar errores antes de relanzarlos.
|
|
56
|
+
*/
|
|
57
|
+
private readonly onError?;
|
|
58
|
+
constructor(
|
|
59
|
+
/**
|
|
60
|
+
* Port para obtener la hora actual
|
|
61
|
+
*/
|
|
62
|
+
systemClock: ISystemClockPort,
|
|
63
|
+
/**
|
|
64
|
+
* Port relacionado con rotación/naming (según diseño actual).
|
|
65
|
+
* Idealmente: la parte de naming debería estar separada en un "namer".
|
|
66
|
+
*/
|
|
67
|
+
fileRotator: IFileRotatorPort,
|
|
68
|
+
/**
|
|
69
|
+
* Writer por stream
|
|
70
|
+
*/
|
|
71
|
+
streamWriter: ILogStreamWriterPort,
|
|
72
|
+
/**
|
|
73
|
+
* Política de rotación (VO de dominio).
|
|
74
|
+
*/
|
|
75
|
+
rotationPolicy: FileRotationPolicy,
|
|
76
|
+
/**
|
|
77
|
+
* Use-case que ejecuta rotación si corresponde
|
|
78
|
+
*/
|
|
79
|
+
rotateIfNeededUseCase: IRotateIfNeededUseCase,
|
|
80
|
+
/**
|
|
81
|
+
* Use-case que serializa y escribe el log al stream.
|
|
82
|
+
*/
|
|
83
|
+
appendLogUseCase: IAppendLogUseCase,
|
|
84
|
+
/**
|
|
85
|
+
* Use-case que asegura que el directorio base exista.
|
|
86
|
+
*/
|
|
87
|
+
ensureDirectoryUseCase: IEnsureDirectoryUseCase,
|
|
88
|
+
/**
|
|
89
|
+
* Hook opcional para reportar/registrar errores antes de relanzarlos.
|
|
90
|
+
*/
|
|
91
|
+
onError?: ((error: Error) => void | Promise<void>) | undefined);
|
|
92
|
+
/**
|
|
93
|
+
* Ejecuta el flujo principal de persistencia del log.
|
|
94
|
+
*/
|
|
95
|
+
execute(dto: SaveLogRequest): Promise<void>;
|
|
96
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersistLogUseCase = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* PersistLogUseCase
|
|
6
|
+
* -----------------------------------------------------------------------------
|
|
7
|
+
* Caso de uso de Application que orquesta el flujo completo:
|
|
8
|
+
* - asegura el directorio base
|
|
9
|
+
* - decide si rota según política y fecha actual
|
|
10
|
+
* - garantiza que el stream está abierto en el archivo correcto
|
|
11
|
+
* - escribe el log (append)
|
|
12
|
+
*
|
|
13
|
+
* Nota (Clean Architecture):
|
|
14
|
+
* - No hace IO directamente: todo IO va por ports/use-cases delegados.
|
|
15
|
+
* - Centraliza la secuencia y manejo de errores del flujo.
|
|
16
|
+
*/
|
|
17
|
+
class PersistLogUseCase {
|
|
18
|
+
constructor(
|
|
19
|
+
/**
|
|
20
|
+
* Port para obtener la hora actual
|
|
21
|
+
*/
|
|
22
|
+
systemClock,
|
|
23
|
+
/**
|
|
24
|
+
* Port relacionado con rotación/naming (según diseño actual).
|
|
25
|
+
* Idealmente: la parte de naming debería estar separada en un "namer".
|
|
26
|
+
*/
|
|
27
|
+
fileRotator,
|
|
28
|
+
/**
|
|
29
|
+
* Writer por stream
|
|
30
|
+
*/
|
|
31
|
+
streamWriter,
|
|
32
|
+
/**
|
|
33
|
+
* Política de rotación (VO de dominio).
|
|
34
|
+
*/
|
|
35
|
+
rotationPolicy,
|
|
36
|
+
/**
|
|
37
|
+
* Use-case que ejecuta rotación si corresponde
|
|
38
|
+
*/
|
|
39
|
+
rotateIfNeededUseCase,
|
|
40
|
+
/**
|
|
41
|
+
* Use-case que serializa y escribe el log al stream.
|
|
42
|
+
*/
|
|
43
|
+
appendLogUseCase,
|
|
44
|
+
/**
|
|
45
|
+
* Use-case que asegura que el directorio base exista.
|
|
46
|
+
*/
|
|
47
|
+
ensureDirectoryUseCase,
|
|
48
|
+
/**
|
|
49
|
+
* Hook opcional para reportar/registrar errores antes de relanzarlos.
|
|
50
|
+
*/
|
|
51
|
+
onError) {
|
|
52
|
+
this.systemClock = systemClock;
|
|
53
|
+
this.fileRotator = fileRotator;
|
|
54
|
+
this.streamWriter = streamWriter;
|
|
55
|
+
this.rotationPolicy = rotationPolicy;
|
|
56
|
+
this.rotateIfNeededUseCase = rotateIfNeededUseCase;
|
|
57
|
+
this.appendLogUseCase = appendLogUseCase;
|
|
58
|
+
this.ensureDirectoryUseCase = ensureDirectoryUseCase;
|
|
59
|
+
this.onError = onError;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ejecuta el flujo principal de persistencia del log.
|
|
63
|
+
*/
|
|
64
|
+
async execute(dto) {
|
|
65
|
+
try {
|
|
66
|
+
// 1) Asegurar que el directorio base existe (si está habilitado)
|
|
67
|
+
await this.ensureDirectoryUseCase.execute();
|
|
68
|
+
// 2) Obtener "ahora" desde un port (testable)
|
|
69
|
+
const currentDate = this.systemClock.now();
|
|
70
|
+
// 3) Verificar si corresponde rotar según política/fecha
|
|
71
|
+
await this.rotateIfNeededUseCase.execute({
|
|
72
|
+
currentDate,
|
|
73
|
+
rotationPolicy: this.rotationPolicy,
|
|
74
|
+
});
|
|
75
|
+
// 4) Asegurar que el stream esté abierto en la ruta esperada
|
|
76
|
+
const expectedPath = this.fileRotator.getExpectedPathForDate(currentDate);
|
|
77
|
+
const currentPath = this.streamWriter.getCurrentPath();
|
|
78
|
+
// Reabrir si:
|
|
79
|
+
// - no está abierto
|
|
80
|
+
// - no hay path actual
|
|
81
|
+
// - el path actual no coincide con el esperado (por fecha/patrón)
|
|
82
|
+
if (
|
|
83
|
+
// Cerrar si estaba abierto (evita handles colgando)
|
|
84
|
+
!this.streamWriter.isOpen() ||
|
|
85
|
+
!currentPath ||
|
|
86
|
+
!currentPath.equals(expectedPath)) {
|
|
87
|
+
if (this.streamWriter.isOpen()) {
|
|
88
|
+
await this.streamWriter.close();
|
|
89
|
+
}
|
|
90
|
+
// Abrir stream al archivo esperado
|
|
91
|
+
await this.streamWriter.open(expectedPath);
|
|
92
|
+
}
|
|
93
|
+
// 5) Escribir el log (serialización + newline + write)
|
|
94
|
+
await this.appendLogUseCase.execute(dto);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Hook opcional (log/metrics), pero se relanza el error para no ocultarlo
|
|
98
|
+
if (this.onError) {
|
|
99
|
+
await this.onError(error);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.PersistLogUseCase = PersistLogUseCase;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ILogStreamWriterPort, IFileRotatorPort } from "../../domain/ports";
|
|
2
|
+
import { FilePath } from "../../domain/value-objects";
|
|
3
|
+
import { RotateIfNeededRequest } from "../dto";
|
|
4
|
+
/**
|
|
5
|
+
* Contrato del caso de uso: retorna el nuevo path si rotó, o null si no rotó.
|
|
6
|
+
*/
|
|
7
|
+
export interface IRotateIfNeededUseCase {
|
|
8
|
+
execute(dto: RotateIfNeededRequest): Promise<FilePath | null>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* RotateIfNeededUseCase
|
|
12
|
+
* -----------------------------------------------------------------------------
|
|
13
|
+
* Caso de uso de Application que:
|
|
14
|
+
* - determina si se debe rotar (según policy + fecha)
|
|
15
|
+
* - si aplica, cambia el writer al nuevo archivo (close/open)
|
|
16
|
+
* - ejecuta un hook opcional onRotate(oldPath, newPath)
|
|
17
|
+
*
|
|
18
|
+
* Nota:
|
|
19
|
+
* - Depende de ports (IO indirecto).
|
|
20
|
+
* - Existe potencial de duplicidad si otro use-case también asegura open/close.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RotateIfNeededUseCase implements IRotateIfNeededUseCase {
|
|
23
|
+
/**
|
|
24
|
+
* Componente de rotación/naming (según diseño actual).
|
|
25
|
+
*/
|
|
26
|
+
private readonly fileRotator;
|
|
27
|
+
/**
|
|
28
|
+
* Writer por stream usado para cerrar/abrir el archivo activo.
|
|
29
|
+
*/
|
|
30
|
+
private readonly fileSystemWriter;
|
|
31
|
+
/**
|
|
32
|
+
* Hook opcional ejecutado cuando efectivamente se rota.
|
|
33
|
+
*/
|
|
34
|
+
private readonly onRotate?;
|
|
35
|
+
constructor(
|
|
36
|
+
/**
|
|
37
|
+
* Componente de rotación/naming (según diseño actual).
|
|
38
|
+
*/
|
|
39
|
+
fileRotator: IFileRotatorPort,
|
|
40
|
+
/**
|
|
41
|
+
* Writer por stream usado para cerrar/abrir el archivo activo.
|
|
42
|
+
*/
|
|
43
|
+
fileSystemWriter: ILogStreamWriterPort,
|
|
44
|
+
/**
|
|
45
|
+
* Hook opcional ejecutado cuando efectivamente se rota.
|
|
46
|
+
*/
|
|
47
|
+
onRotate?: ((oldPath: FilePath, newPath: FilePath) => void | Promise<void>) | undefined);
|
|
48
|
+
/**
|
|
49
|
+
* Ejecuta el flujo "rotar si hace falta".
|
|
50
|
+
* Retorna:
|
|
51
|
+
* - null si no corresponde rotar
|
|
52
|
+
* - newPath si rotó (o si el expected path es el nuevo destino)
|
|
53
|
+
*/
|
|
54
|
+
execute(dto: RotateIfNeededRequest): Promise<FilePath | null>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RotateIfNeededUseCase = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* RotateIfNeededUseCase
|
|
6
|
+
* -----------------------------------------------------------------------------
|
|
7
|
+
* Caso de uso de Application que:
|
|
8
|
+
* - determina si se debe rotar (según policy + fecha)
|
|
9
|
+
* - si aplica, cambia el writer al nuevo archivo (close/open)
|
|
10
|
+
* - ejecuta un hook opcional onRotate(oldPath, newPath)
|
|
11
|
+
*
|
|
12
|
+
* Nota:
|
|
13
|
+
* - Depende de ports (IO indirecto).
|
|
14
|
+
* - Existe potencial de duplicidad si otro use-case también asegura open/close.
|
|
15
|
+
*/
|
|
16
|
+
class RotateIfNeededUseCase {
|
|
17
|
+
constructor(
|
|
18
|
+
/**
|
|
19
|
+
* Componente de rotación/naming (según diseño actual).
|
|
20
|
+
*/
|
|
21
|
+
fileRotator,
|
|
22
|
+
/**
|
|
23
|
+
* Writer por stream usado para cerrar/abrir el archivo activo.
|
|
24
|
+
*/
|
|
25
|
+
fileSystemWriter,
|
|
26
|
+
/**
|
|
27
|
+
* Hook opcional ejecutado cuando efectivamente se rota.
|
|
28
|
+
*/
|
|
29
|
+
onRotate) {
|
|
30
|
+
this.fileRotator = fileRotator;
|
|
31
|
+
this.fileSystemWriter = fileSystemWriter;
|
|
32
|
+
this.onRotate = onRotate;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ejecuta el flujo "rotar si hace falta".
|
|
36
|
+
* Retorna:
|
|
37
|
+
* - null si no corresponde rotar
|
|
38
|
+
* - newPath si rotó (o si el expected path es el nuevo destino)
|
|
39
|
+
*/
|
|
40
|
+
async execute(dto) {
|
|
41
|
+
const { currentDate, rotationPolicy } = dto;
|
|
42
|
+
// Decide si corresponde rotar (según implementación actual del port)
|
|
43
|
+
const shouldRotate = await this.fileRotator.shouldRotate(rotationPolicy, currentDate);
|
|
44
|
+
if (!shouldRotate) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Path anterior (si existe) y path esperado para la fecha actual
|
|
48
|
+
const oldPath = this.fileRotator.getCurrentPath();
|
|
49
|
+
const newPath = this.fileRotator.getExpectedPathForDate(currentDate);
|
|
50
|
+
// Solo si hay cambio real de archivo, se reabre el writer
|
|
51
|
+
if (oldPath && !oldPath.equals(newPath)) {
|
|
52
|
+
await this.fileSystemWriter.close();
|
|
53
|
+
await this.fileSystemWriter.open(newPath);
|
|
54
|
+
if (this.onRotate) {
|
|
55
|
+
await this.onRotate(oldPath, newPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return newPath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.RotateIfNeededUseCase = RotateIfNeededUseCase;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./log-entry.model";
|
|
@@ -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("./log-entry.model"), exports);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FilePath } from "../../value-objects";
|
|
2
|
+
/**
|
|
3
|
+
* IFilePathPort
|
|
4
|
+
* -----------------------------------------------------------------------------
|
|
5
|
+
* Puerto de dominio para:
|
|
6
|
+
* 1) Construir un FilePath (Value Object) desde entradas string externas.
|
|
7
|
+
* 2) Componer rutas a partir de un FilePath existente.
|
|
8
|
+
*
|
|
9
|
+
* Motivación (Clean Architecture):
|
|
10
|
+
* - El dominio/aplicación NO dependen de `node:path` ni de reglas de OS.
|
|
11
|
+
* - Infraestructura provee un adapter (ej. SystemFilePathAdapter) que implementa esto.
|
|
12
|
+
*
|
|
13
|
+
* Nota de consistencia:
|
|
14
|
+
* - Define y respeta si FilePath en tu dominio es SIEMPRE absoluto o puede ser relativo.
|
|
15
|
+
* (Si es siempre absoluto, fromRaw debería resolver a absoluto.)
|
|
16
|
+
*/
|
|
17
|
+
export interface IFilePathPort {
|
|
18
|
+
/**
|
|
19
|
+
* Construye un FilePath a partir de una ruta cruda (relativa o absoluta).
|
|
20
|
+
*
|
|
21
|
+
* Responsabilidades del adapter:
|
|
22
|
+
* - Validar input mínimo (string no vacío).
|
|
23
|
+
* - Normalizar separadores / limpiar.
|
|
24
|
+
* - (Opcional según tu modelo) Resolver a ruta absoluta.
|
|
25
|
+
*/
|
|
26
|
+
fromRaw(inputPath: string): FilePath;
|
|
27
|
+
/**
|
|
28
|
+
* Une segmentos a partir de un FilePath base y retorna un nuevo FilePath (inmutable).
|
|
29
|
+
*
|
|
30
|
+
* Caso típico:
|
|
31
|
+
* - basePath (FilePath) + "subdir" + "file.log"
|
|
32
|
+
*
|
|
33
|
+
* Responsabilidades del adapter:
|
|
34
|
+
* - Validar segmentos (no vacíos si aplica).
|
|
35
|
+
* - Normalizar la ruta resultante.
|
|
36
|
+
*/
|
|
37
|
+
join(filePath: FilePath, ...segments: string[]): FilePath;
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FileSize, FilePath, FileRotationPolicy } from "../../value-objects";
|
|
2
|
+
/**
|
|
3
|
+
* IFileRotatorPort
|
|
4
|
+
* -----------------------------------------------------------------------------
|
|
5
|
+
* Puerto de dominio para consultar información del filesystem necesaria
|
|
6
|
+
* para implementar rotación de archivos (logs) en casos de uso/servicios.
|
|
7
|
+
*
|
|
8
|
+
* Importante (Clean Architecture):
|
|
9
|
+
* - Este port NO debe contener reglas de negocio (p. ej. decidir si rotar).
|
|
10
|
+
* - Debe limitarse a capacidades del mundo externo (filesystem).
|
|
11
|
+
*
|
|
12
|
+
* La decisión de rotación (por fecha/tamaño/política) debe vivir en:
|
|
13
|
+
* - un Domain Service (reglas), o
|
|
14
|
+
* - un Use Case (orquestación), pero no dentro del port.
|
|
15
|
+
*/
|
|
16
|
+
export interface IFileRotatorPort {
|
|
17
|
+
/**
|
|
18
|
+
* Obtiene el archivo de log "actual" si existe/está determinado
|
|
19
|
+
* por la implementación (por ejemplo, por configuración).
|
|
20
|
+
*
|
|
21
|
+
* Nota:
|
|
22
|
+
* - Si el "current" depende solo de config, considera eliminar esto
|
|
23
|
+
* y que el caso de uso lo derive desde config/naming.
|
|
24
|
+
*/
|
|
25
|
+
getCurrentPath(): FilePath | null;
|
|
26
|
+
/**
|
|
27
|
+
* Calcular cómo se va a llamar el archivo según una fecha
|
|
28
|
+
* @param date Fecha para la que se quiere el path
|
|
29
|
+
*/
|
|
30
|
+
getExpectedPathForDate(date: Date): FilePath;
|
|
31
|
+
/**
|
|
32
|
+
* Consulta el tamaño del archivo en filesystem.
|
|
33
|
+
* (IO puro; ideal para políticas de rotación por tamaño).
|
|
34
|
+
*/
|
|
35
|
+
getFileSize(filePath: FilePath): Promise<FileSize>;
|
|
36
|
+
/**
|
|
37
|
+
* Decidir si corresponde rotar de acuerdo a una RotationPolicy y la fecha actual
|
|
38
|
+
* @param policy Política de rotación
|
|
39
|
+
* @param currentDate Fecha actual
|
|
40
|
+
*/
|
|
41
|
+
shouldRotate(policy: FileRotationPolicy, currentDate: Date): Promise<boolean>;
|
|
42
|
+
}
|
|
@@ -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-path.port"), exports);
|
|
18
|
+
__exportStar(require("./file-rotator.port"), exports);
|