@jmlq/logger-plugin-fs 0.1.0-alpha.5 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -153
- 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/infrastructure/{datasources → 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/{domain/types → infrastructure/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 +39 -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
package/package.json
CHANGED
|
@@ -1,34 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmlq/logger-plugin-fs",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"description": "Filesystem plugin for JMLQ Logger implementing Clean Architecture principles.",
|
|
4
|
+
"version": "0.1.0-alpha.7",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist"
|
|
9
|
-
],
|
|
10
7
|
"scripts": {
|
|
11
|
-
"
|
|
8
|
+
"dev": "rimraf dist && mkdir dist && tsc -p tsconfig.json",
|
|
9
|
+
"build": "rimraf dist && mkdir dist && tsc -p tsconfig.build.json",
|
|
12
10
|
"prepublishOnly": "npm run build",
|
|
13
11
|
"test": "jest --passWithNoTests",
|
|
14
12
|
"test:watch": "jest --watch",
|
|
15
|
-
"test:
|
|
13
|
+
"test:coverage": "jest --coverage",
|
|
14
|
+
"example:help": "tsx examples/index.example.ts help",
|
|
15
|
+
"example:all": "tsx examples/index.example.ts",
|
|
16
|
+
"example:value-objects": "tsx examples/index.example.ts value-objects",
|
|
17
|
+
"example:infrastructure-policies": "tsx examples/index.example.ts infrastructure-policies",
|
|
18
|
+
"example:infrastructure-adapters": "tsx examples/index.example.ts infrastructure-adapters",
|
|
19
|
+
"example:application-use-cases": "tsx examples/index.example.ts application-use-cases",
|
|
20
|
+
"example:application-services": "tsx examples/index.example.ts application-services",
|
|
21
|
+
"example:application-factories": "tsx examples/index.example.ts application-factories",
|
|
22
|
+
"example:filesize": "tsx examples/filesize.example.ts"
|
|
16
23
|
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"logger",
|
|
26
|
+
"filesystem",
|
|
27
|
+
"clean-architecture",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"author": "MLahuasi",
|
|
31
|
+
"license": "MIT",
|
|
17
32
|
"devDependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"@
|
|
22
|
-
"@
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"@jmlq/logger": "^0.1.0-alpha.5"
|
|
33
|
+
"@swc/core": "^1.3.95",
|
|
34
|
+
"@swc/jest": "^0.2.29",
|
|
35
|
+
"@types/bcryptjs": "^2.4.5",
|
|
36
|
+
"@types/jest": "^29.5.8",
|
|
37
|
+
"@types/node": "^20.8.10",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"rimraf": "^6.1.2",
|
|
40
|
+
"tsx": "^4.1.4",
|
|
41
|
+
"typescript": "^5.2.2"
|
|
28
42
|
},
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"architecture.md",
|
|
47
|
+
"install.md"
|
|
48
|
+
],
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@jmlq/logger": ">=0.1.0-alpha.12"
|
|
33
51
|
}
|
|
34
52
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { IFsSerializer, IStreamWriterPort } from "../../domain/contracts";
|
|
2
|
-
import { SaveLogDTO } from "../dto";
|
|
3
|
-
export declare class AppendLogUseCase {
|
|
4
|
-
private readonly serializer;
|
|
5
|
-
constructor(serializer: IFsSerializer);
|
|
6
|
-
execute(input: SaveLogDTO, writer: IStreamWriterPort): Promise<void>;
|
|
7
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Serializa (línea + '\n') y escribe en el stream con manejo de backpressure.
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.AppendLogUseCase = void 0;
|
|
5
|
-
class AppendLogUseCase {
|
|
6
|
-
constructor(serializer) {
|
|
7
|
-
this.serializer = serializer;
|
|
8
|
-
}
|
|
9
|
-
async execute(input, writer) {
|
|
10
|
-
// 1) Serializa el log a texto + salto de línea
|
|
11
|
-
const line = this.serializer.serialize(input.log) + "\n";
|
|
12
|
-
// 2) Intenta escribir; si devuelve false, espera 'drain'
|
|
13
|
-
const ok = writer.write(line);
|
|
14
|
-
if (!ok) {
|
|
15
|
-
await new Promise((resolve) => writer.once("drain", resolve));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
exports.AppendLogUseCase = AppendLogUseCase;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { AppendLogUseCase, RotateIfNeededUseCase } from ".";
|
|
2
|
-
import { IFileRotatorPort, IStreamWriterPort } from "../../domain/contracts";
|
|
3
|
-
import { SaveLogDTO } from "../dto";
|
|
4
|
-
export declare class PersistLogUseCase {
|
|
5
|
-
private readonly rotateUC;
|
|
6
|
-
private readonly appendUC;
|
|
7
|
-
private readonly rotator;
|
|
8
|
-
private readonly openWriter;
|
|
9
|
-
private readonly onRotate?;
|
|
10
|
-
private readonly onError?;
|
|
11
|
-
constructor(rotateUC: RotateIfNeededUseCase, // caso de uso de rotación
|
|
12
|
-
appendUC: AppendLogUseCase, // caso de uso de escritura
|
|
13
|
-
rotator: IFileRotatorPort, // puerto para actualizar activePath
|
|
14
|
-
openWriter: (path: string) => IStreamWriterPort, // factory: abre stream
|
|
15
|
-
onRotate?: ((oldPath: string, newPath: string) => void | Promise<void>) | undefined, onError?: ((err: unknown) => void | Promise<void>) | undefined);
|
|
16
|
-
private writer;
|
|
17
|
-
private path;
|
|
18
|
-
private ensureWriter;
|
|
19
|
-
private disposeWriter;
|
|
20
|
-
execute(input: SaveLogDTO): Promise<void>;
|
|
21
|
-
flush(): Promise<void>;
|
|
22
|
-
dispose(): Promise<void>;
|
|
23
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
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;
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
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;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./options.type";
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
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 });
|
|
@@ -1,14 +0,0 @@
|
|
|
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;
|
|
@@ -1,7 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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;
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
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;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./fs.datasource";
|
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
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;
|
|
@@ -1,15 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { IFsProvider } from ".";
|
|
2
|
-
import type { IStreamWriterPort } from "../../domain/contracts";
|
|
3
|
-
export declare class FsWriterAdapter implements IStreamWriterPort {
|
|
4
|
-
private readonly stream;
|
|
5
|
-
constructor(fs: IFsProvider, path: string);
|
|
6
|
-
write(line: string): boolean;
|
|
7
|
-
once(event: "drain" | "error", cb: (...a: any[]) => void): void;
|
|
8
|
-
get writableNeedDrain(): boolean | undefined;
|
|
9
|
-
end(cb: () => void): void;
|
|
10
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FsWriterAdapter = void 0;
|
|
4
|
-
class FsWriterAdapter {
|
|
5
|
-
constructor(fs, path) {
|
|
6
|
-
// Abre el stream en modo append ('a') para no truncar archivos existentes
|
|
7
|
-
this.stream = fs.createWriteStream(path, { flags: "a" });
|
|
8
|
-
}
|
|
9
|
-
write(line) {
|
|
10
|
-
// Escribe la línea; devuelve false si hay backpressure
|
|
11
|
-
return this.stream.write(line);
|
|
12
|
-
}
|
|
13
|
-
once(event, cb) {
|
|
14
|
-
// Suscribe un handler de un solo uso para 'drain' o 'error'
|
|
15
|
-
this.stream.once(event, cb);
|
|
16
|
-
}
|
|
17
|
-
get writableNeedDrain() {
|
|
18
|
-
// expone flag de backpressure si está presente
|
|
19
|
-
return this.stream.writableNeedDrain;
|
|
20
|
-
}
|
|
21
|
-
end(cb) {
|
|
22
|
-
// Cierra el stream drenando el buffer pendiente
|
|
23
|
-
this.stream.end(cb);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
exports.FsWriterAdapter = FsWriterAdapter;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Utilidades puras de path y formateo de nombre.
|
|
3
|
-
// Cambiamos a getters UTC para evitar desfaces por huso horario.
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.formatPattern = formatPattern;
|
|
6
|
-
exports.splitBaseExt = splitBaseExt;
|
|
7
|
-
exports.joinBase = joinBase;
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
function formatPattern(pattern, now) {
|
|
10
|
-
// Usar UTC para que "2025-09-09T00:00:00Z" siga siendo 2025-09-09
|
|
11
|
-
const yyyy = now.getUTCFullYear().toString();
|
|
12
|
-
const MM = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
13
|
-
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
14
|
-
return pattern
|
|
15
|
-
.replace("{yyyy}", yyyy)
|
|
16
|
-
.replace("{MM}", MM)
|
|
17
|
-
.replace("{dd}", dd);
|
|
18
|
-
}
|
|
19
|
-
function splitBaseExt(fileName) {
|
|
20
|
-
const ext = (0, path_1.extname)(fileName);
|
|
21
|
-
const base = ext ? (0, path_1.basename)(fileName, ext) : fileName;
|
|
22
|
-
return { base, ext };
|
|
23
|
-
}
|
|
24
|
-
function joinBase(basePath, fileName) {
|
|
25
|
-
return (0, path_1.join)(basePath, fileName);
|
|
26
|
-
}
|