@jmlq/logger-plugin-fs 0.1.0-alpha.6 → 0.1.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -117
- package/architecture.md +426 -0
- package/dist/application/dto/index.d.ts +2 -0
- package/dist/application/dto/index.js +2 -0
- package/dist/application/dto/rotation-check.dto.d.ts +5 -0
- package/dist/application/dto/save-log.dto.d.ts +3 -3
- package/dist/application/dto/write-operation.dto.d.ts +5 -0
- package/dist/application/factory/create-fs-datasource.factory.d.ts +3 -0
- package/dist/application/factory/create-fs-datasource.factory.js +26 -0
- package/dist/application/factory/index.d.ts +1 -0
- package/dist/{presentation → application}/factory/index.js +1 -1
- package/dist/application/services/fs-datasource.service.d.ts +7 -5
- package/dist/application/services/fs-datasource.service.js +6 -9
- package/dist/application/use-cases/append-log.use-case.d.ts +12 -0
- package/dist/application/use-cases/append-log.use-case.js +26 -0
- package/dist/application/use-cases/ensure-directory.use-case.d.ts +11 -0
- package/dist/application/use-cases/ensure-directory.use-case.js +27 -0
- package/dist/application/use-cases/index.d.ts +4 -3
- package/dist/application/use-cases/index.js +4 -3
- package/dist/application/use-cases/persist-log.use-case.d.ts +19 -0
- package/dist/application/use-cases/persist-log.use-case.js +47 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +17 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.js +28 -0
- package/dist/domain/ports/clock.port.d.ts +8 -0
- package/dist/domain/ports/file-path-adapter.port.d.ts +33 -0
- package/dist/domain/ports/fs-provider.port.d.ts +61 -0
- package/dist/domain/ports/fs-provider.port.js +2 -0
- package/dist/domain/ports/index.d.ts +4 -0
- package/dist/domain/{contracts → ports}/index.js +3 -3
- package/dist/domain/ports/stream-writer.port.d.ts +42 -0
- package/dist/domain/ports/stream-writer.port.js +2 -0
- package/dist/domain/value-objects/file-path.vo.d.ts +27 -0
- package/dist/domain/value-objects/file-path.vo.js +59 -0
- package/dist/domain/value-objects/file-size.vo.d.ts +14 -0
- package/dist/domain/value-objects/file-size.vo.js +57 -0
- package/dist/domain/value-objects/index.d.ts +2 -2
- package/dist/domain/value-objects/index.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +14 -5
- package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +20 -0
- package/dist/infrastructure/adapters/file-rotator.adapter.js +105 -0
- package/dist/infrastructure/adapters/fs-provider.adapter.d.ts +17 -0
- package/dist/infrastructure/{fs/fs-provider.js → adapters/fs-provider.adapter.js} +41 -19
- package/dist/infrastructure/adapters/fs-writer.adapter.d.ts +13 -0
- package/dist/infrastructure/adapters/fs-writer.adapter.js +71 -0
- package/dist/infrastructure/{fs → adapters}/index.d.ts +2 -2
- package/dist/infrastructure/{fs → adapters}/index.js +2 -2
- package/dist/infrastructure/adapters/node-clock.adapter.d.ts +4 -0
- package/dist/infrastructure/{fs → adapters}/node-clock.adapter.js +0 -1
- package/dist/infrastructure/adapters/node-file-path.adapter.d.ts +16 -0
- package/dist/infrastructure/adapters/node-file-path.adapter.js +117 -0
- package/dist/infrastructure/filesystem/index.d.ts +4 -0
- package/dist/infrastructure/filesystem/index.js +20 -0
- package/dist/infrastructure/filesystem/polices/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/polices/index.js +5 -0
- package/dist/infrastructure/filesystem/polices/rotation-policy.d.ts +29 -0
- package/dist/infrastructure/filesystem/polices/rotation-policy.js +55 -0
- package/dist/infrastructure/filesystem/ports/file-rotator.port.d.ts +32 -0
- package/dist/infrastructure/filesystem/ports/file-rotator.port.js +2 -0
- package/dist/infrastructure/filesystem/ports/index.d.ts +1 -0
- package/dist/{domain/types → infrastructure/filesystem/ports}/index.js +1 -1
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +49 -0
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +19 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
- package/dist/infrastructure/filesystem/types/filesystem-serializer.type.d.ts +10 -0
- package/dist/infrastructure/filesystem/types/filesystem-serializer.type.js +2 -0
- package/dist/infrastructure/filesystem/types/index.d.ts +3 -0
- package/dist/infrastructure/filesystem/types/index.js +19 -0
- package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.d.ts +22 -0
- package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.js +37 -0
- package/dist/infrastructure/filesystem/value-objects/index.d.ts +1 -0
- package/dist/infrastructure/{datasources → filesystem/value-objects}/index.js +1 -1
- package/dist/shared/errors/file-operation.error.d.ts +12 -0
- package/dist/shared/errors/file-operation.error.js +32 -0
- package/dist/shared/errors/fs-plugin.error.d.ts +4 -0
- package/dist/shared/errors/fs-plugin.error.js +11 -0
- package/dist/shared/errors/index.d.ts +3 -0
- package/dist/shared/errors/index.js +19 -0
- package/dist/shared/errors/rotation.error.d.ts +10 -0
- package/dist/shared/errors/rotation.error.js +25 -0
- package/install.md +520 -0
- package/package.json +38 -21
- package/LICENSE +0 -21
- package/dist/application/use-cases/append-log.usecase.d.ts +0 -7
- package/dist/application/use-cases/append-log.usecase.js +0 -19
- package/dist/application/use-cases/persist-log.usecase.d.ts +0 -23
- package/dist/application/use-cases/persist-log.usecase.js +0 -74
- package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +0 -10
- package/dist/application/use-cases/rotate-if-needed.usecase.js +0 -39
- package/dist/domain/contracts/clock.contract.d.ts +0 -3
- package/dist/domain/contracts/file-rotator.contract.d.ts +0 -7
- package/dist/domain/contracts/index.d.ts +0 -4
- package/dist/domain/contracts/serializer.contract.d.ts +0 -3
- package/dist/domain/contracts/stream-writer.port.d.ts +0 -6
- package/dist/domain/types/index.d.ts +0 -1
- package/dist/domain/types/options.type.d.ts +0 -11
- package/dist/domain/types/options.type.js +0 -5
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +0 -4
- package/dist/domain/value-objects/file-name-pattern.vo.js +0 -14
- package/dist/domain/value-objects/rotation-policy.vo.d.ts +0 -7
- package/dist/domain/value-objects/rotation-policy.vo.js +0 -20
- package/dist/infrastructure/datasources/fs.datasource.d.ts +0 -17
- package/dist/infrastructure/datasources/fs.datasource.js +0 -84
- package/dist/infrastructure/datasources/index.d.ts +0 -1
- package/dist/infrastructure/fs/file-rotator.adapter.d.ts +0 -22
- package/dist/infrastructure/fs/file-rotator.adapter.js +0 -51
- package/dist/infrastructure/fs/fs-provider.d.ts +0 -15
- package/dist/infrastructure/fs/fs-writer.adapter.d.ts +0 -10
- package/dist/infrastructure/fs/fs-writer.adapter.js +0 -26
- package/dist/infrastructure/fs/node-clock.adapter.d.ts +0 -4
- package/dist/infrastructure/fs/path-utils.d.ts +0 -6
- package/dist/infrastructure/fs/path-utils.js +0 -26
- package/dist/presentation/factory/create-fs-datasource.d.ts +0 -15
- package/dist/presentation/factory/create-fs-datasource.js +0 -39
- package/dist/presentation/factory/index.d.ts +0 -1
- /package/dist/{domain/contracts/clock.contract.js → application/dto/rotation-check.dto.js} +0 -0
- /package/dist/{domain/contracts/file-rotator.contract.js → application/dto/write-operation.dto.js} +0 -0
- /package/dist/domain/{contracts/serializer.contract.js → ports/clock.port.js} +0 -0
- /package/dist/domain/{contracts/stream-writer.port.js → ports/file-path-adapter.port.js} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IClockPort, IStreamWriterPort } from "../../domain/ports";
|
|
2
|
+
import { ISaveLogDto } from "../dto";
|
|
3
|
+
import { IAppendLogUseCase, IEnsureDirectoryUseCase, IRotateIfNeededUseCase } from ".";
|
|
4
|
+
import { IFileRotatorPort, RotationPolicy } from "../../infrastructure/filesystem";
|
|
5
|
+
export interface IPersistLogUseCase {
|
|
6
|
+
execute(dto: ISaveLogDto): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare class PersistLogUseCase implements IPersistLogUseCase {
|
|
9
|
+
private readonly clockPort;
|
|
10
|
+
private readonly fileRotatorPort;
|
|
11
|
+
private readonly streamWriterPort;
|
|
12
|
+
private readonly rotationPolicy;
|
|
13
|
+
private readonly rotateIfNeededUseCase;
|
|
14
|
+
private readonly appendLogUseCase;
|
|
15
|
+
private readonly ensureDirectoryUseCase;
|
|
16
|
+
private readonly onError?;
|
|
17
|
+
constructor(clockPort: IClockPort, fileRotatorPort: IFileRotatorPort, streamWriterPort: IStreamWriterPort, rotationPolicy: RotationPolicy, rotateIfNeededUseCase: IRotateIfNeededUseCase, appendLogUseCase: IAppendLogUseCase, ensureDirectoryUseCase: IEnsureDirectoryUseCase, onError?: ((error: Error) => void | Promise<void>) | undefined);
|
|
18
|
+
execute(dto: ISaveLogDto): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersistLogUseCase = void 0;
|
|
4
|
+
class PersistLogUseCase {
|
|
5
|
+
constructor(clockPort, fileRotatorPort, streamWriterPort, rotationPolicy, rotateIfNeededUseCase, appendLogUseCase, ensureDirectoryUseCase, onError) {
|
|
6
|
+
this.clockPort = clockPort;
|
|
7
|
+
this.fileRotatorPort = fileRotatorPort;
|
|
8
|
+
this.streamWriterPort = streamWriterPort;
|
|
9
|
+
this.rotationPolicy = rotationPolicy;
|
|
10
|
+
this.rotateIfNeededUseCase = rotateIfNeededUseCase;
|
|
11
|
+
this.appendLogUseCase = appendLogUseCase;
|
|
12
|
+
this.ensureDirectoryUseCase = ensureDirectoryUseCase;
|
|
13
|
+
this.onError = onError;
|
|
14
|
+
}
|
|
15
|
+
async execute(dto) {
|
|
16
|
+
try {
|
|
17
|
+
// Asegurar que el directorio existe
|
|
18
|
+
await this.ensureDirectoryUseCase.execute();
|
|
19
|
+
const currentDate = this.clockPort.now();
|
|
20
|
+
// Verificar si necesitamos rotar
|
|
21
|
+
await this.rotateIfNeededUseCase.execute({
|
|
22
|
+
currentDate,
|
|
23
|
+
rotationPolicy: this.rotationPolicy,
|
|
24
|
+
});
|
|
25
|
+
// Asegurar que el stream está abierto en la ruta correcta
|
|
26
|
+
const expectedPath = this.fileRotatorPort.getExpectedPathForDate(currentDate);
|
|
27
|
+
const currentPath = this.streamWriterPort.getCurrentPath();
|
|
28
|
+
if (!this.streamWriterPort.isOpen() ||
|
|
29
|
+
!currentPath ||
|
|
30
|
+
!currentPath.equals(expectedPath)) {
|
|
31
|
+
if (this.streamWriterPort.isOpen()) {
|
|
32
|
+
await this.streamWriterPort.close();
|
|
33
|
+
}
|
|
34
|
+
await this.streamWriterPort.open(expectedPath);
|
|
35
|
+
}
|
|
36
|
+
// Escribir el log
|
|
37
|
+
await this.appendLogUseCase.execute(dto);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (this.onError) {
|
|
41
|
+
await this.onError(error);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.PersistLogUseCase = PersistLogUseCase;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IFileRotatorPort, RotationPolicy } from "../../infrastructure/filesystem";
|
|
2
|
+
import { IStreamWriterPort } from "../../domain/ports";
|
|
3
|
+
import { FilePath } from "../../domain/value-objects";
|
|
4
|
+
export interface IRotateIfNeededDto {
|
|
5
|
+
currentDate: Date;
|
|
6
|
+
rotationPolicy: RotationPolicy;
|
|
7
|
+
}
|
|
8
|
+
export interface IRotateIfNeededUseCase {
|
|
9
|
+
execute(dto: IRotateIfNeededDto): Promise<FilePath | null>;
|
|
10
|
+
}
|
|
11
|
+
export declare class RotateIfNeededUseCase implements IRotateIfNeededUseCase {
|
|
12
|
+
private readonly fileRotatorPort;
|
|
13
|
+
private readonly streamWriterPort;
|
|
14
|
+
private readonly onRotate?;
|
|
15
|
+
constructor(fileRotatorPort: IFileRotatorPort, streamWriterPort: IStreamWriterPort, onRotate?: ((oldPath: FilePath, newPath: FilePath) => void | Promise<void>) | undefined);
|
|
16
|
+
execute(dto: IRotateIfNeededDto): Promise<FilePath | null>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RotateIfNeededUseCase = void 0;
|
|
4
|
+
class RotateIfNeededUseCase {
|
|
5
|
+
constructor(fileRotatorPort, streamWriterPort, onRotate) {
|
|
6
|
+
this.fileRotatorPort = fileRotatorPort;
|
|
7
|
+
this.streamWriterPort = streamWriterPort;
|
|
8
|
+
this.onRotate = onRotate;
|
|
9
|
+
}
|
|
10
|
+
async execute(dto) {
|
|
11
|
+
const { currentDate, rotationPolicy } = dto;
|
|
12
|
+
const shouldRotate = await this.fileRotatorPort.shouldRotate(rotationPolicy, currentDate);
|
|
13
|
+
if (!shouldRotate) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const oldPath = this.fileRotatorPort.getCurrentPath();
|
|
17
|
+
const newPath = this.fileRotatorPort.getExpectedPathForDate(currentDate);
|
|
18
|
+
if (oldPath && !oldPath.equals(newPath)) {
|
|
19
|
+
await this.streamWriterPort.close();
|
|
20
|
+
await this.streamWriterPort.open(newPath);
|
|
21
|
+
if (this.onRotate) {
|
|
22
|
+
await this.onRotate(oldPath, newPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return newPath;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.RotateIfNeededUseCase = RotateIfNeededUseCase;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Representa una abstracción del reloj del sistema dentro de la capa domain.
|
|
3
|
+
* Su único propósito es permitir que el dominio pueda obtener la hora actual
|
|
4
|
+
* sin depender directamente de new Date() ni de Date.now()
|
|
5
|
+
*/
|
|
6
|
+
export interface IClockPort {
|
|
7
|
+
now(): Date;
|
|
8
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FilePath } from "../value-objects";
|
|
2
|
+
export interface IFilePathAdapterPort {
|
|
3
|
+
/**
|
|
4
|
+
* Crea un FilePath a partir de una ruta cruda (relativa o absoluta).
|
|
5
|
+
* La implementación se encarga de normalizar y resolver a absoluta.
|
|
6
|
+
*/
|
|
7
|
+
fromRaw(inputPath: string): FilePath;
|
|
8
|
+
/**
|
|
9
|
+
* Crea un FilePath a partir de (directorio, basename, extensión).
|
|
10
|
+
*/
|
|
11
|
+
fromBasename(directory: string, basename: string, extension: string): FilePath;
|
|
12
|
+
/**
|
|
13
|
+
* Devuelve un nuevo FilePath con la misma ruta pero otra extensión.
|
|
14
|
+
*/
|
|
15
|
+
withExtension(filePath: FilePath, newExtension: string): FilePath;
|
|
16
|
+
/**
|
|
17
|
+
* Devuelve un nuevo FilePath con el mismo directorio y extensión,
|
|
18
|
+
* pero con otro basename.
|
|
19
|
+
*/
|
|
20
|
+
withBasename(filePath: FilePath, newBasename: string): FilePath;
|
|
21
|
+
/**
|
|
22
|
+
* Une segmentos a la ruta actual.
|
|
23
|
+
*/
|
|
24
|
+
join(filePath: FilePath, ...segments: string[]): FilePath;
|
|
25
|
+
/**
|
|
26
|
+
* Resuelve segmentos relativos respecto al FilePath actual.
|
|
27
|
+
*/
|
|
28
|
+
resolve(filePath: FilePath, ...segments: string[]): FilePath;
|
|
29
|
+
/**
|
|
30
|
+
* Obtiene la ruta relativa desde basePath hacia el FilePath.
|
|
31
|
+
*/
|
|
32
|
+
relativeTo(basePath: string, filePath: FilePath): string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Es una abstracción del sistema de archivos (fs) que la capa de dominio puede solicitar sin depender directamente del módulo fs de Node
|
|
3
|
+
*/
|
|
4
|
+
export interface IFsProviderPort {
|
|
5
|
+
/**
|
|
6
|
+
* Permite saber si un archivo o carpeta existe
|
|
7
|
+
* @param path Ruta del archivo o directorio
|
|
8
|
+
*/
|
|
9
|
+
exists(path: string): Promise<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* Permite crear directorios
|
|
12
|
+
* @param path Ruta del directorio
|
|
13
|
+
* @param options Opciones de creación. Actualmente solo soporta 'recursive' (Indica si se deben crear directorios padres si no existen)
|
|
14
|
+
*/
|
|
15
|
+
mkdir(path: string, options?: {
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Se usa para leer registros existentes
|
|
20
|
+
* @param path Ruta del archivo
|
|
21
|
+
*/
|
|
22
|
+
readFile(path: string): Promise<Buffer>;
|
|
23
|
+
/**
|
|
24
|
+
* Se usa para escribir registros nuevos
|
|
25
|
+
* @param path Ruta del archivo
|
|
26
|
+
* @param data Datos a escribir
|
|
27
|
+
*/
|
|
28
|
+
writeFile(path: string, data: string | Buffer): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Se usa para agregar registros nuevos al final del archivo
|
|
31
|
+
* @param path Ruta del archivo
|
|
32
|
+
* @param data Datos a agregar
|
|
33
|
+
*/
|
|
34
|
+
appendFile(path: string, data: string | Buffer): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Permite obtener metadata del archivo o directorio
|
|
37
|
+
* - Ver tamaño actual → política de RotationPolicy.
|
|
38
|
+
* - Ver fecha de modificación → rotación diaria/semanal.
|
|
39
|
+
* @param path Ruta del archivo o directorio
|
|
40
|
+
*/
|
|
41
|
+
stat(path: string): Promise<{
|
|
42
|
+
size: number;
|
|
43
|
+
mtime: Date;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Necesario para rotación por índice y descubrimiento de archivos app.log.1, app.log.2, etc.
|
|
47
|
+
* @param path Ruta del directorio
|
|
48
|
+
*/
|
|
49
|
+
readdir(path: string): Promise<string[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Eliminar archivos antiguos.
|
|
52
|
+
* @param path Ruta del archivo
|
|
53
|
+
*/
|
|
54
|
+
unlink(path: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Clave para renombrar archivos cuando rotan.
|
|
57
|
+
* @param oldPath Ruta actual del archivo o directorio
|
|
58
|
+
* @param newPath Nueva ruta del archivo o directorio
|
|
59
|
+
*/
|
|
60
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
61
|
+
}
|
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./clock.
|
|
18
|
-
__exportStar(require("./
|
|
19
|
-
__exportStar(require("./file-rotator.contract"), exports);
|
|
17
|
+
__exportStar(require("./clock.port"), exports);
|
|
18
|
+
__exportStar(require("./fs-provider.port"), exports);
|
|
20
19
|
__exportStar(require("./stream-writer.port"), exports);
|
|
20
|
+
__exportStar(require("./file-path-adapter.port"), exports);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FilePath } from "../value-objects";
|
|
2
|
+
/**
|
|
3
|
+
* Es una abstracción de un writer basado en stream, típicamente usado para escribir logs de forma eficiente sin reabrir archivos por cada línea.
|
|
4
|
+
* Es un Outbound Port del dominio hacia una implementación de infraestructura
|
|
5
|
+
*/
|
|
6
|
+
export interface IStreamWriterPort {
|
|
7
|
+
/**
|
|
8
|
+
* Escritura directa de datos al stream
|
|
9
|
+
* @param data Datos a escribir
|
|
10
|
+
* @return Retorna true si la escritura fue exitosa, false si el buffer está lleno y se debe esperar a que se vacíe antes de escribir más
|
|
11
|
+
*/
|
|
12
|
+
write(data: string): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Abre un stream para un archivo específico.
|
|
15
|
+
* @param filePath Ruta del archivo a abrir para escritura como FilePath
|
|
16
|
+
*/
|
|
17
|
+
open(filePath: FilePath): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Cierra el stream sin finalizar el archivo, dejando posibilidad de reabrirlo o rotarlo
|
|
20
|
+
* @return Promise que se resuelve cuando el stream ha sido cerrado
|
|
21
|
+
*/
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Buffer pendiente (si la implementación usa buffering).
|
|
25
|
+
* Asegura que todos los datos en el buffer hayan sido escritos al archivo
|
|
26
|
+
*/
|
|
27
|
+
flush(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Permite saber si el stream está listo para aceptar escritura
|
|
30
|
+
* @return true si el stream está abierto y listo, false si está cerrado o no disponible
|
|
31
|
+
*/
|
|
32
|
+
isOpen(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Permite al dominio saber qué archivo está actualmente siendo usado para escribir
|
|
35
|
+
* @return FilePath del archivo actualmente abierto, o null si no hay ninguno abierto
|
|
36
|
+
*/
|
|
37
|
+
getCurrentPath(): FilePath | null;
|
|
38
|
+
/**
|
|
39
|
+
* Finaliza el stream
|
|
40
|
+
*/
|
|
41
|
+
end(): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface IFilePathProps {
|
|
2
|
+
absolutePath: string;
|
|
3
|
+
directory: string;
|
|
4
|
+
filename: string;
|
|
5
|
+
extension: string;
|
|
6
|
+
basename: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Value Object que representa una ruta de archivo ya normalizada.
|
|
10
|
+
* No depende de Node ni del módulo `path`. Solo encapsula datos inmutables.
|
|
11
|
+
*/
|
|
12
|
+
export declare class FilePath {
|
|
13
|
+
private readonly _absolutePath;
|
|
14
|
+
private readonly _directory;
|
|
15
|
+
private readonly _filename;
|
|
16
|
+
private readonly _extension;
|
|
17
|
+
private readonly _basename;
|
|
18
|
+
constructor(props: IFilePathProps);
|
|
19
|
+
get value(): string;
|
|
20
|
+
get absolutePath(): string;
|
|
21
|
+
get directory(): string;
|
|
22
|
+
get filename(): string;
|
|
23
|
+
get extension(): string;
|
|
24
|
+
get basename(): string;
|
|
25
|
+
equals(other: FilePath): boolean;
|
|
26
|
+
toString(): string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FilePath = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Value Object que representa una ruta de archivo ya normalizada.
|
|
6
|
+
* No depende de Node ni del módulo `path`. Solo encapsula datos inmutables.
|
|
7
|
+
*/
|
|
8
|
+
class FilePath {
|
|
9
|
+
constructor(props) {
|
|
10
|
+
const { absolutePath, directory, filename, extension, basename } = props;
|
|
11
|
+
if (!absolutePath ||
|
|
12
|
+
typeof absolutePath !== "string" ||
|
|
13
|
+
!absolutePath.trim()) {
|
|
14
|
+
throw new Error("FilePath: absolutePath must be a non-empty string");
|
|
15
|
+
}
|
|
16
|
+
if (!directory || typeof directory !== "string" || !directory.trim()) {
|
|
17
|
+
throw new Error("FilePath: directory must be a non-empty string");
|
|
18
|
+
}
|
|
19
|
+
if (!filename || typeof filename !== "string" || !filename.trim()) {
|
|
20
|
+
throw new Error("FilePath: filename must be a non-empty string");
|
|
21
|
+
}
|
|
22
|
+
if (typeof extension !== "string") {
|
|
23
|
+
throw new Error("FilePath: extension must be a string");
|
|
24
|
+
}
|
|
25
|
+
if (!basename || typeof basename !== "string" || !basename.trim()) {
|
|
26
|
+
throw new Error("FilePath: basename must be a non-empty string");
|
|
27
|
+
}
|
|
28
|
+
this._absolutePath = absolutePath;
|
|
29
|
+
this._directory = directory;
|
|
30
|
+
this._filename = filename;
|
|
31
|
+
this._extension = extension;
|
|
32
|
+
this._basename = basename;
|
|
33
|
+
}
|
|
34
|
+
get value() {
|
|
35
|
+
return this._absolutePath;
|
|
36
|
+
}
|
|
37
|
+
get absolutePath() {
|
|
38
|
+
return this._absolutePath;
|
|
39
|
+
}
|
|
40
|
+
get directory() {
|
|
41
|
+
return this._directory;
|
|
42
|
+
}
|
|
43
|
+
get filename() {
|
|
44
|
+
return this._filename;
|
|
45
|
+
}
|
|
46
|
+
get extension() {
|
|
47
|
+
return this._extension;
|
|
48
|
+
}
|
|
49
|
+
get basename() {
|
|
50
|
+
return this._basename;
|
|
51
|
+
}
|
|
52
|
+
equals(other) {
|
|
53
|
+
return this._absolutePath === other._absolutePath;
|
|
54
|
+
}
|
|
55
|
+
toString() {
|
|
56
|
+
return this._absolutePath;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.FilePath = FilePath;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class FileSize {
|
|
2
|
+
private readonly _bytes;
|
|
3
|
+
constructor(bytes: number);
|
|
4
|
+
get bytes(): number;
|
|
5
|
+
get kilobytes(): number;
|
|
6
|
+
get megabytes(): number;
|
|
7
|
+
get gigabytes(): number;
|
|
8
|
+
static fromMegabytes(mb: number): FileSize;
|
|
9
|
+
static fromKilobytes(kb: number): FileSize;
|
|
10
|
+
isGreaterThan(other: FileSize): boolean;
|
|
11
|
+
isLessThan(other: FileSize): boolean;
|
|
12
|
+
equals(other: FileSize): boolean;
|
|
13
|
+
toString(): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileSize = void 0;
|
|
4
|
+
class FileSize {
|
|
5
|
+
constructor(bytes) {
|
|
6
|
+
if (bytes < 0 || !Number.isInteger(bytes)) {
|
|
7
|
+
throw new Error("FileSize: bytes must be a non-negative integer");
|
|
8
|
+
}
|
|
9
|
+
this._bytes = bytes;
|
|
10
|
+
}
|
|
11
|
+
get bytes() {
|
|
12
|
+
return this._bytes;
|
|
13
|
+
}
|
|
14
|
+
get kilobytes() {
|
|
15
|
+
return this._bytes / 1024;
|
|
16
|
+
}
|
|
17
|
+
get megabytes() {
|
|
18
|
+
return this._bytes / (1024 * 1024);
|
|
19
|
+
}
|
|
20
|
+
get gigabytes() {
|
|
21
|
+
return this._bytes / (1024 * 1024 * 1024);
|
|
22
|
+
}
|
|
23
|
+
static fromMegabytes(mb) {
|
|
24
|
+
if (mb < 0) {
|
|
25
|
+
throw new Error("FileSize: megabytes must be non-negative");
|
|
26
|
+
}
|
|
27
|
+
return new FileSize(Math.floor(mb * 1024 * 1024));
|
|
28
|
+
}
|
|
29
|
+
static fromKilobytes(kb) {
|
|
30
|
+
if (kb < 0) {
|
|
31
|
+
throw new Error("FileSize: kilobytes must be non-negative");
|
|
32
|
+
}
|
|
33
|
+
return new FileSize(Math.floor(kb * 1024));
|
|
34
|
+
}
|
|
35
|
+
isGreaterThan(other) {
|
|
36
|
+
return this._bytes > other._bytes;
|
|
37
|
+
}
|
|
38
|
+
isLessThan(other) {
|
|
39
|
+
return this._bytes < other._bytes;
|
|
40
|
+
}
|
|
41
|
+
equals(other) {
|
|
42
|
+
return this._bytes === other._bytes;
|
|
43
|
+
}
|
|
44
|
+
toString() {
|
|
45
|
+
if (this._bytes >= 1024 * 1024 * 1024) {
|
|
46
|
+
return `${(this._bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
47
|
+
}
|
|
48
|
+
if (this._bytes >= 1024 * 1024) {
|
|
49
|
+
return `${(this._bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
50
|
+
}
|
|
51
|
+
if (this._bytes >= 1024) {
|
|
52
|
+
return `${(this._bytes / 1024).toFixed(2)} KB`;
|
|
53
|
+
}
|
|
54
|
+
return `${this._bytes} bytes`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.FileSize = FileSize;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./file-
|
|
2
|
-
export * from "./
|
|
1
|
+
export * from "./file-path.vo";
|
|
2
|
+
export * from "./file-size.vo";
|
|
@@ -14,5 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./file-
|
|
18
|
-
__exportStar(require("./
|
|
17
|
+
__exportStar(require("./file-path.vo"), exports);
|
|
18
|
+
__exportStar(require("./file-size.vo"), exports);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { createFsDatasource } from "./
|
|
3
|
-
export {
|
|
1
|
+
export { FilePath, FileSize } from "./domain/value-objects";
|
|
2
|
+
export { createFsDatasource } from "./application/factory";
|
|
3
|
+
export { FsPluginError, FileOperationError, RotationError, } from "./shared/errors";
|
|
4
|
+
export { IFilesystemDatasourceOptions, FsRotationBy, IFileRotationConfig, IFsSerializer, FileNamePattern, RotationPolicy, } from "./infrastructure/filesystem";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FileNamePattern = exports.createFsDatasource = exports.
|
|
3
|
+
exports.RotationPolicy = exports.FileNamePattern = exports.RotationError = exports.FileOperationError = exports.FsPluginError = exports.createFsDatasource = exports.FileSize = exports.FilePath = void 0;
|
|
4
|
+
// Value Objects
|
|
4
5
|
var value_objects_1 = require("./domain/value-objects");
|
|
5
|
-
Object.defineProperty(exports, "
|
|
6
|
-
|
|
6
|
+
Object.defineProperty(exports, "FilePath", { enumerable: true, get: function () { return value_objects_1.FilePath; } });
|
|
7
|
+
Object.defineProperty(exports, "FileSize", { enumerable: true, get: function () { return value_objects_1.FileSize; } });
|
|
8
|
+
// Factory
|
|
9
|
+
var factory_1 = require("./application/factory");
|
|
7
10
|
Object.defineProperty(exports, "createFsDatasource", { enumerable: true, get: function () { return factory_1.createFsDatasource; } });
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// Errors
|
|
12
|
+
var errors_1 = require("./shared/errors");
|
|
13
|
+
Object.defineProperty(exports, "FsPluginError", { enumerable: true, get: function () { return errors_1.FsPluginError; } });
|
|
14
|
+
Object.defineProperty(exports, "FileOperationError", { enumerable: true, get: function () { return errors_1.FileOperationError; } });
|
|
15
|
+
Object.defineProperty(exports, "RotationError", { enumerable: true, get: function () { return errors_1.RotationError; } });
|
|
16
|
+
var filesystem_1 = require("./infrastructure/filesystem");
|
|
17
|
+
Object.defineProperty(exports, "FileNamePattern", { enumerable: true, get: function () { return filesystem_1.FileNamePattern; } });
|
|
18
|
+
Object.defineProperty(exports, "RotationPolicy", { enumerable: true, get: function () { return filesystem_1.RotationPolicy; } });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IFsProviderPort, IFilePathAdapterPort } from "../../domain/ports";
|
|
2
|
+
import { FileSize, FilePath } from "../../domain/value-objects";
|
|
3
|
+
import { FileNamePattern, IFileRotatorPort, RotationPolicy } from "../filesystem";
|
|
4
|
+
export declare class FileRotatorAdapter implements IFileRotatorPort {
|
|
5
|
+
private readonly fsProvider;
|
|
6
|
+
private readonly filePathAdapter;
|
|
7
|
+
private readonly fileNamePattern;
|
|
8
|
+
private readonly basePath;
|
|
9
|
+
private currentFilePath;
|
|
10
|
+
constructor(fsProvider: IFsProviderPort, filePathAdapter: IFilePathAdapterPort, fileNamePattern: FileNamePattern, basePath: string);
|
|
11
|
+
getCurrentPath(): FilePath | null;
|
|
12
|
+
getExpectedPathForDate(date: Date): FilePath;
|
|
13
|
+
private buildPathForDate;
|
|
14
|
+
getFileSize(filePath: FilePath): Promise<FileSize>;
|
|
15
|
+
getFileSizeBytes(path: string): Promise<number>;
|
|
16
|
+
getNextRotationIndex(baseFilePath: FilePath): Promise<number>;
|
|
17
|
+
shouldRotate(policy: RotationPolicy, currentDate: Date): Promise<boolean>;
|
|
18
|
+
private shouldRotateByDay;
|
|
19
|
+
private shouldRotateBySize;
|
|
20
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileRotatorAdapter = void 0;
|
|
4
|
+
const value_objects_1 = require("../../domain/value-objects");
|
|
5
|
+
class FileRotatorAdapter {
|
|
6
|
+
constructor(fsProvider, filePathAdapter, fileNamePattern, basePath) {
|
|
7
|
+
this.fsProvider = fsProvider;
|
|
8
|
+
this.filePathAdapter = filePathAdapter;
|
|
9
|
+
this.fileNamePattern = fileNamePattern;
|
|
10
|
+
this.basePath = basePath;
|
|
11
|
+
this.currentFilePath = null;
|
|
12
|
+
}
|
|
13
|
+
getCurrentPath() {
|
|
14
|
+
return this.currentFilePath;
|
|
15
|
+
}
|
|
16
|
+
getExpectedPathForDate(date) {
|
|
17
|
+
const fullFilePath = this.buildPathForDate(date);
|
|
18
|
+
this.currentFilePath = fullFilePath;
|
|
19
|
+
return fullFilePath;
|
|
20
|
+
}
|
|
21
|
+
buildPathForDate(date) {
|
|
22
|
+
const pattern = this.fileNamePattern.pattern;
|
|
23
|
+
const replacements = {
|
|
24
|
+
"{yyyy}": date.getFullYear().toString(),
|
|
25
|
+
"{MM}": (date.getMonth() + 1).toString().padStart(2, "0"),
|
|
26
|
+
"{dd}": date.getDate().toString().padStart(2, "0"),
|
|
27
|
+
"{HH}": date.getHours().toString().padStart(2, "0"),
|
|
28
|
+
"{mm}": date.getMinutes().toString().padStart(2, "0"),
|
|
29
|
+
"{ss}": date.getSeconds().toString().padStart(2, "0"),
|
|
30
|
+
};
|
|
31
|
+
let fileName = pattern;
|
|
32
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
33
|
+
fileName = fileName.replace(new RegExp(token.replace(/[{}]/g, "\\$&"), "g"), value);
|
|
34
|
+
}
|
|
35
|
+
return this.filePathAdapter.join(this.filePathAdapter.fromRaw(this.basePath), fileName);
|
|
36
|
+
}
|
|
37
|
+
async getFileSize(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
const stats = await this.fsProvider.stat(filePath.absolutePath);
|
|
40
|
+
return new value_objects_1.FileSize(stats.size);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return new value_objects_1.FileSize(0);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Método de compatibilidad - deprecado, usar getFileSize
|
|
47
|
+
async getFileSizeBytes(path) {
|
|
48
|
+
const filePath = this.filePathAdapter.fromRaw(path);
|
|
49
|
+
const fileSize = await this.getFileSize(filePath);
|
|
50
|
+
return fileSize.bytes;
|
|
51
|
+
}
|
|
52
|
+
async getNextRotationIndex(baseFilePath) {
|
|
53
|
+
try {
|
|
54
|
+
const files = await this.fsProvider.readdir(baseFilePath.directory);
|
|
55
|
+
const pattern = new RegExp(`^${baseFilePath.basename}\\.(\\d+)${baseFilePath.extension.replace(".", "\\.")}$`);
|
|
56
|
+
let maxIndex = 0;
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const match = file.match(pattern);
|
|
59
|
+
if (match) {
|
|
60
|
+
const index = parseInt(match[1], 10);
|
|
61
|
+
if (index > maxIndex) {
|
|
62
|
+
maxIndex = index;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return maxIndex + 1;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async shouldRotate(policy, currentDate) {
|
|
73
|
+
if (policy.by === "none") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (policy.by === "day") {
|
|
77
|
+
return this.shouldRotateByDay(currentDate);
|
|
78
|
+
}
|
|
79
|
+
if (policy.by === "size") {
|
|
80
|
+
return this.shouldRotateBySize(policy);
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
shouldRotateByDay(currentDate) {
|
|
85
|
+
if (!this.currentFilePath) {
|
|
86
|
+
return true; // No hay archivo actual, necesitamos crear uno
|
|
87
|
+
}
|
|
88
|
+
// Ahora solo calculamos, NO mutamos currentFilePath
|
|
89
|
+
const expectedPath = this.buildPathForDate(currentDate);
|
|
90
|
+
return !this.currentFilePath.equals(expectedPath);
|
|
91
|
+
}
|
|
92
|
+
async shouldRotateBySize(policy) {
|
|
93
|
+
if (!this.currentFilePath || !policy.maxSize) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const currentFileSize = await this.getFileSize(this.currentFilePath);
|
|
98
|
+
return policy.shouldRotateBySize(currentFileSize);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.FileRotatorAdapter = FileRotatorAdapter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IFsProviderPort } from "../../domain/ports";
|
|
2
|
+
export declare class FsProviderAdapter implements IFsProviderPort {
|
|
3
|
+
exists(path: string): Promise<boolean>;
|
|
4
|
+
mkdir(path: string, options?: {
|
|
5
|
+
recursive?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
readFile(path: string): Promise<Buffer>;
|
|
8
|
+
writeFile(path: string, data: string | Buffer): Promise<void>;
|
|
9
|
+
appendFile(path: string, data: string | Buffer): Promise<void>;
|
|
10
|
+
stat(path: string): Promise<{
|
|
11
|
+
size: number;
|
|
12
|
+
mtime: Date;
|
|
13
|
+
}>;
|
|
14
|
+
readdir(path: string): Promise<string[]>;
|
|
15
|
+
unlink(path: string): Promise<void>;
|
|
16
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
17
|
+
}
|