@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
package/package.json
CHANGED
|
@@ -1,35 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmlq/logger-plugin-fs",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"license": "MIT",
|
|
3
|
+
"description": "Filesystem plugin for JMLQ Logger implementing Clean Architecture principles.",
|
|
4
|
+
"version": "0.1.0-alpha.8",
|
|
6
5
|
"main": "dist/index.js",
|
|
7
6
|
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist"
|
|
10
|
-
],
|
|
11
7
|
"scripts": {
|
|
12
|
-
"
|
|
8
|
+
"dev": "rimraf dist && mkdir dist && tsc -p tsconfig.json",
|
|
9
|
+
"build": "rimraf dist && mkdir dist && tsc -p tsconfig.build.json",
|
|
13
10
|
"prepublishOnly": "npm run build",
|
|
14
11
|
"test": "jest --passWithNoTests",
|
|
15
12
|
"test:watch": "jest --watch",
|
|
16
|
-
"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"
|
|
17
23
|
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"logger",
|
|
26
|
+
"filesystem",
|
|
27
|
+
"clean-architecture",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"author": "MLahuasi",
|
|
31
|
+
"license": "MIT",
|
|
18
32
|
"devDependencies": {
|
|
19
|
-
"@
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"@
|
|
23
|
-
"@
|
|
24
|
-
"
|
|
25
|
-
"
|
|
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"
|
|
26
42
|
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"architecture.md",
|
|
47
|
+
"install.md"
|
|
48
|
+
],
|
|
27
49
|
"dependencies": {
|
|
28
|
-
"@jmlq/logger": "
|
|
29
|
-
},
|
|
30
|
-
"overrides": {
|
|
31
|
-
"test-exclude": "^7.0.1",
|
|
32
|
-
"glob": "^10.4.5",
|
|
33
|
-
"minimatch": "^9.0.5"
|
|
50
|
+
"@jmlq/logger": ">=0.1.0-alpha.12"
|
|
34
51
|
}
|
|
35
52
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024-2025 mlahuasi
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the “Software”), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
|
@@ -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;
|