@jmlq/logger-plugin-fs 0.1.0-alpha.3 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -241
- package/dist/application/dto/index.d.ts +1 -0
- package/dist/application/dto/index.js +17 -0
- package/dist/application/dto/save-log.dto.d.ts +4 -0
- package/dist/application/dto/save-log.dto.js +2 -0
- package/dist/application/services/fs-datasource.service.d.ts +9 -0
- package/dist/application/services/fs-datasource.service.js +22 -0
- package/dist/application/services/index.d.ts +1 -0
- package/dist/application/services/index.js +17 -0
- package/dist/application/use-cases/append-log.usecase.d.ts +7 -0
- package/dist/application/use-cases/append-log.usecase.js +19 -0
- package/dist/application/use-cases/index.d.ts +3 -0
- package/dist/application/use-cases/index.js +19 -0
- package/dist/application/use-cases/persist-log.usecase.d.ts +23 -0
- package/dist/application/use-cases/persist-log.usecase.js +74 -0
- package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +10 -0
- package/dist/application/use-cases/rotate-if-needed.usecase.js +39 -0
- package/dist/domain/contracts/clock.contract.d.ts +3 -0
- package/dist/domain/contracts/clock.contract.js +2 -0
- package/dist/domain/contracts/file-rotator.contract.d.ts +7 -0
- package/dist/domain/contracts/file-rotator.contract.js +2 -0
- package/dist/domain/contracts/index.d.ts +4 -0
- package/dist/domain/contracts/index.js +20 -0
- package/dist/domain/contracts/serializer.contract.d.ts +3 -0
- package/dist/domain/contracts/serializer.contract.js +2 -0
- package/dist/domain/contracts/stream-writer.port.d.ts +6 -0
- package/dist/domain/contracts/stream-writer.port.js +2 -0
- package/dist/domain/types/index.d.ts +1 -0
- package/dist/domain/types/index.js +17 -0
- package/dist/domain/types/options.type.d.ts +11 -0
- package/dist/domain/types/options.type.js +5 -0
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +4 -0
- package/dist/domain/value-objects/file-name-pattern.vo.js +14 -0
- package/dist/domain/value-objects/index.d.ts +2 -0
- package/dist/domain/value-objects/index.js +18 -0
- package/dist/domain/value-objects/rotation-policy.vo.d.ts +7 -0
- package/dist/domain/value-objects/rotation-policy.vo.js +20 -0
- package/dist/index.d.ts +3 -13
- package/dist/index.js +7 -38
- package/dist/infrastructure/datasources/fs.datasource.d.ts +17 -0
- package/dist/infrastructure/datasources/fs.datasource.js +84 -0
- package/dist/infrastructure/datasources/index.d.ts +1 -0
- package/dist/infrastructure/datasources/index.js +17 -0
- package/dist/infrastructure/fs/file-rotator.adapter.d.ts +22 -0
- package/dist/infrastructure/fs/file-rotator.adapter.js +51 -0
- package/dist/infrastructure/fs/fs-provider.d.ts +15 -0
- package/dist/infrastructure/fs/fs-provider.js +55 -0
- package/dist/infrastructure/fs/fs-writer.adapter.d.ts +10 -0
- package/dist/infrastructure/fs/fs-writer.adapter.js +26 -0
- package/dist/infrastructure/fs/index.d.ts +5 -0
- package/dist/infrastructure/fs/index.js +21 -0
- package/dist/infrastructure/fs/node-clock.adapter.d.ts +4 -0
- package/dist/infrastructure/fs/node-clock.adapter.js +10 -0
- package/dist/infrastructure/fs/path-utils.d.ts +6 -0
- package/dist/infrastructure/fs/path-utils.js +26 -0
- package/dist/presentation/factory/create-fs-datasource.d.ts +15 -0
- package/dist/presentation/factory/create-fs-datasource.js +39 -0
- package/dist/presentation/factory/index.d.ts +1 -0
- package/dist/presentation/factory/index.js +17 -0
- package/package.json +15 -3
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Orquesta: (1) rotar si hace falta, (2) escribir línea, (3) hooks de rotación/errores.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.PersistLogUseCase = void 0;
|
|
5
|
+
class PersistLogUseCase {
|
|
6
|
+
constructor(rotateUC, // caso de uso de rotación
|
|
7
|
+
appendUC, // caso de uso de escritura
|
|
8
|
+
rotator, // puerto para actualizar activePath
|
|
9
|
+
openWriter, // factory: abre stream
|
|
10
|
+
onRotate, onError) {
|
|
11
|
+
this.rotateUC = rotateUC;
|
|
12
|
+
this.appendUC = appendUC;
|
|
13
|
+
this.rotator = rotator;
|
|
14
|
+
this.openWriter = openWriter;
|
|
15
|
+
this.onRotate = onRotate;
|
|
16
|
+
this.onError = onError;
|
|
17
|
+
this.writer = null; // writer activo
|
|
18
|
+
this.path = null; // path activo
|
|
19
|
+
}
|
|
20
|
+
// Inicializa el writer si no existe (idempotente)
|
|
21
|
+
ensureWriter(currentPath) {
|
|
22
|
+
if (!this.writer) {
|
|
23
|
+
this.writer = this.openWriter(currentPath);
|
|
24
|
+
this.path = currentPath;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Cierra writer activo de forma segura
|
|
28
|
+
async disposeWriter() {
|
|
29
|
+
if (!this.writer)
|
|
30
|
+
return;
|
|
31
|
+
await new Promise((resolve) => this.writer.end(resolve));
|
|
32
|
+
this.writer = null;
|
|
33
|
+
}
|
|
34
|
+
// Persistir un log con rotación condicional
|
|
35
|
+
async execute(input) {
|
|
36
|
+
try {
|
|
37
|
+
// 1) Asegurar writer inicial para el path actual del rotator
|
|
38
|
+
const currentPath = this.rotator.getActiveFilePath();
|
|
39
|
+
this.ensureWriter(currentPath);
|
|
40
|
+
// 2) Consultar si debemos rotar → abrir nuevo writer si hace falta
|
|
41
|
+
const nextPath = await this.rotateUC.execute();
|
|
42
|
+
if (nextPath) {
|
|
43
|
+
const old = this.path;
|
|
44
|
+
await this.disposeWriter();
|
|
45
|
+
this.rotator.setActivePath(nextPath);
|
|
46
|
+
this.writer = this.openWriter(nextPath);
|
|
47
|
+
this.path = nextPath;
|
|
48
|
+
// Notificar rotación (no bloqueante)
|
|
49
|
+
Promise.resolve(this.onRotate?.(old, nextPath)).catch((e) => this.onError?.(e));
|
|
50
|
+
}
|
|
51
|
+
// 3) Escribir línea manejando backpressure
|
|
52
|
+
await this.appendUC.execute(input, this.writer);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// Notificar error sin tumbar el proceso (política del plugin)
|
|
56
|
+
await Promise.resolve(this.onError?.(err));
|
|
57
|
+
// Si quieres propagar al core, descomenta:
|
|
58
|
+
// throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Esperar drenaje del buffer
|
|
62
|
+
async flush() {
|
|
63
|
+
if (!this.writer)
|
|
64
|
+
return;
|
|
65
|
+
if (this.writer.writableNeedDrain) {
|
|
66
|
+
await new Promise((resolve) => this.writer.once("drain", resolve));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Cerrar el writer activo
|
|
70
|
+
async dispose() {
|
|
71
|
+
await this.disposeWriter();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.PersistLogUseCase = PersistLogUseCase;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IClock, IFileRotatorPort } from "../../domain/contracts";
|
|
2
|
+
import { RotationPolicy } from "../../domain/value-objects";
|
|
3
|
+
export declare class RotateIfNeededUseCase {
|
|
4
|
+
private readonly policy;
|
|
5
|
+
private readonly rotator;
|
|
6
|
+
private readonly clock;
|
|
7
|
+
private readonly maxSizeBytes?;
|
|
8
|
+
constructor(policy: RotationPolicy, rotator: IFileRotatorPort, clock: IClock, maxSizeBytes?: number | undefined);
|
|
9
|
+
execute(): Promise<string | null>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Caso de uso: evaluar si corresponde rotar el archivo y, si corresponde, devolver el nuevo path.
|
|
3
|
+
// NO toca node:fs directamente; delega a servicios de infraestructura (rotator).
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.RotateIfNeededUseCase = void 0;
|
|
6
|
+
class RotateIfNeededUseCase {
|
|
7
|
+
// Dependencias: política (VO), puerto de rotación y reloj.
|
|
8
|
+
constructor(policy, rotator, clock, maxSizeBytes // cache de bytes (derivado de policy.maxSizeMB)
|
|
9
|
+
) {
|
|
10
|
+
this.policy = policy;
|
|
11
|
+
this.rotator = rotator;
|
|
12
|
+
this.clock = clock;
|
|
13
|
+
this.maxSizeBytes = maxSizeBytes;
|
|
14
|
+
}
|
|
15
|
+
// Devuelve null si no se requiere rotar; o el "nextPath" a usar si hay rotación.
|
|
16
|
+
async execute() {
|
|
17
|
+
// Selecciona estrategia según la política
|
|
18
|
+
if (this.policy.by === "none")
|
|
19
|
+
return null;
|
|
20
|
+
if (this.policy.by === "day") {
|
|
21
|
+
// Compara path actual vs path esperado para "hoy"
|
|
22
|
+
const now = this.clock.now();
|
|
23
|
+
const expected = this.rotator.expectedPathForToday(now);
|
|
24
|
+
return expected !== this.rotator.getActiveFilePath() ? expected : null;
|
|
25
|
+
}
|
|
26
|
+
if (this.policy.by === "size") {
|
|
27
|
+
// Si el archivo supera el umbral, pedimos un nuevo path indexado
|
|
28
|
+
const current = this.rotator.getActiveFilePath();
|
|
29
|
+
const size = await this.rotator.sizeOf(current);
|
|
30
|
+
const limit = this.maxSizeBytes ?? this.policy.maxSizeMB * 1024 * 1024;
|
|
31
|
+
if ((size ?? 0) >= limit) {
|
|
32
|
+
return await this.rotator.nextIndexedPath();
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.RotateIfNeededUseCase = RotateIfNeededUseCase;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./clock.contract"), exports);
|
|
18
|
+
__exportStar(require("./serializer.contract"), exports);
|
|
19
|
+
__exportStar(require("./file-rotator.contract"), exports);
|
|
20
|
+
__exportStar(require("./stream-writer.port"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./options.type";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./options.type"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IFsSerializer } from "../contracts";
|
|
2
|
+
import { FileNamePattern, RotationPolicy } from "../value-objects";
|
|
3
|
+
export interface FsDatasourceOptions {
|
|
4
|
+
basePath: string;
|
|
5
|
+
mkdir?: boolean;
|
|
6
|
+
pattern?: FileNamePattern;
|
|
7
|
+
rotation?: RotationPolicy;
|
|
8
|
+
serializer?: IFsSerializer;
|
|
9
|
+
onRotate?: (oldPath: string, newPath: string) => void | Promise<void>;
|
|
10
|
+
onError?: (err: unknown) => void | Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Tipo de opciones de alto nivel del plugin, expuesto a la "presentación" (factory).
|
|
3
|
+
// Reúne los contratos/VOs de dominio, pero sin detalles de node:fs.
|
|
4
|
+
// Esto mantiene la API pública limpia y testeable.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileNamePattern = void 0;
|
|
4
|
+
// Value Object para el patrón de nombre del archivo.
|
|
5
|
+
// Soporta tokens {yyyy}, {MM}, {dd}; el formateo real ocurre en infraestructura.
|
|
6
|
+
class FileNamePattern {
|
|
7
|
+
constructor(pattern) {
|
|
8
|
+
this.pattern = pattern;
|
|
9
|
+
if (!pattern || typeof pattern !== "string") {
|
|
10
|
+
throw new Error("[FileNamePattern] pattern inválido");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.FileNamePattern = FileNamePattern;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./file-name-pattern.vo"), exports);
|
|
18
|
+
__exportStar(require("./rotation-policy.vo"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type RotationBy = "none" | "day" | "size";
|
|
2
|
+
export declare class RotationPolicy {
|
|
3
|
+
readonly by: RotationBy;
|
|
4
|
+
readonly maxSizeMB?: number | undefined;
|
|
5
|
+
readonly maxFiles?: number | undefined;
|
|
6
|
+
constructor(by: RotationBy, maxSizeMB?: number | undefined, maxFiles?: number | undefined);
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Value Object que expresa la política de rotación del adaptador FS.
|
|
3
|
+
// Esto es propio del dominio del plugin (infra-policy), no del core.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.RotationPolicy = void 0;
|
|
6
|
+
class RotationPolicy {
|
|
7
|
+
// by: modo de rotación
|
|
8
|
+
// maxSizeMB: umbral (sólo aplica cuando by === "size")
|
|
9
|
+
// maxFiles: retención opcional
|
|
10
|
+
constructor(by, maxSizeMB, maxFiles) {
|
|
11
|
+
this.by = by;
|
|
12
|
+
this.maxSizeMB = maxSizeMB;
|
|
13
|
+
this.maxFiles = maxFiles;
|
|
14
|
+
// Validación mínima de invariantes del VO
|
|
15
|
+
if (by === "size" && (!maxSizeMB || maxSizeMB <= 0)) {
|
|
16
|
+
throw new Error("[RotationPolicy] maxSizeMB > 0 es requerido cuando by = 'size'");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.RotationPolicy = RotationPolicy;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
export declare class FileSystemDatasource implements ILogDatasource {
|
|
6
|
-
private readonly opts;
|
|
7
|
-
private stream;
|
|
8
|
-
constructor(opts: FsOpts);
|
|
9
|
-
save(log: ILog): Promise<void>;
|
|
10
|
-
flush(): Promise<void>;
|
|
11
|
-
dispose(): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export {};
|
|
1
|
+
export { RotationPolicy, type RotationBy } from "./domain/value-objects";
|
|
2
|
+
export { createFsDatasource } from "./presentation/factory";
|
|
3
|
+
export { FileNamePattern } from "./domain/value-objects";
|
package/dist/index.js
CHANGED
|
@@ -1,40 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
async save(log) {
|
|
15
|
-
if (!this.stream)
|
|
16
|
-
throw new Error("FS stream closed");
|
|
17
|
-
const line = JSON.stringify(log) + "\n";
|
|
18
|
-
if (!this.stream.write(line)) {
|
|
19
|
-
// backpressure: esperar a 'drain'
|
|
20
|
-
await new Promise((resolve) => this.stream.once("drain", resolve));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async flush() {
|
|
24
|
-
if (!this.stream)
|
|
25
|
-
return;
|
|
26
|
-
if (!this.stream.writableNeedDrain)
|
|
27
|
-
return;
|
|
28
|
-
await new Promise((resolve) => this.stream.once("drain", resolve));
|
|
29
|
-
}
|
|
30
|
-
async dispose() {
|
|
31
|
-
if (!this.stream)
|
|
32
|
-
return;
|
|
33
|
-
await new Promise((resolve, reject) => {
|
|
34
|
-
this.stream.end(() => resolve()); // end -> cierra el fd
|
|
35
|
-
this.stream.once("error", reject);
|
|
36
|
-
});
|
|
37
|
-
this.stream = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
exports.FileSystemDatasource = FileSystemDatasource;
|
|
3
|
+
exports.FileNamePattern = exports.createFsDatasource = exports.RotationPolicy = void 0;
|
|
4
|
+
var value_objects_1 = require("./domain/value-objects");
|
|
5
|
+
Object.defineProperty(exports, "RotationPolicy", { enumerable: true, get: function () { return value_objects_1.RotationPolicy; } });
|
|
6
|
+
var factory_1 = require("./presentation/factory");
|
|
7
|
+
Object.defineProperty(exports, "createFsDatasource", { enumerable: true, get: function () { return factory_1.createFsDatasource; } });
|
|
8
|
+
var value_objects_2 = require("./domain/value-objects");
|
|
9
|
+
Object.defineProperty(exports, "FileNamePattern", { enumerable: true, get: function () { return value_objects_2.FileNamePattern; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ILog, ILogDatasource } from "@jmlq/logger";
|
|
2
|
+
import type { FsDatasourceOptions } from "../../domain/types";
|
|
3
|
+
export declare class FileSystemDatasource implements ILogDatasource {
|
|
4
|
+
private readonly opts;
|
|
5
|
+
private stream;
|
|
6
|
+
private readonly fs;
|
|
7
|
+
private readonly clock;
|
|
8
|
+
private readonly rotator;
|
|
9
|
+
private readonly rotateUC;
|
|
10
|
+
private readonly appendUC;
|
|
11
|
+
private readonly policy;
|
|
12
|
+
private readonly serializer;
|
|
13
|
+
constructor(opts: FsDatasourceOptions);
|
|
14
|
+
save(log: ILog): Promise<void>;
|
|
15
|
+
flush(): Promise<void>;
|
|
16
|
+
dispose(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Datasource que implementa ILogDatasource del core (@jmlq/logger).
|
|
3
|
+
// Orquesta los casos de uso: rotar si hace falta y luego append.
|
|
4
|
+
// Aquí sí interactuamos con node:fs (a través del provider), streams y hooks.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FileSystemDatasource = void 0;
|
|
7
|
+
const fs_1 = require("../fs");
|
|
8
|
+
const use_cases_1 = require("../../application/use-cases");
|
|
9
|
+
const value_objects_1 = require("../../domain/value-objects");
|
|
10
|
+
class FileSystemDatasource {
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.opts = opts;
|
|
13
|
+
this.stream = null; // Stream activo de escritura
|
|
14
|
+
this.clock = new fs_1.NodeClock(); // Reloj real (inyectable si se necesita)
|
|
15
|
+
// 1) Defaults seguros
|
|
16
|
+
this.fs = (0, fs_1.createFsProvider)();
|
|
17
|
+
this.policy = opts.rotation ?? new value_objects_1.RotationPolicy("day");
|
|
18
|
+
const pattern = opts.pattern ?? new value_objects_1.FileNamePattern("app-{yyyy}{MM}{dd}.log");
|
|
19
|
+
// Si el usuario no pasa serializer, usamos JSON.stringify como línea
|
|
20
|
+
this.serializer = opts.serializer ?? {
|
|
21
|
+
serialize: (x) => JSON.stringify(x),
|
|
22
|
+
};
|
|
23
|
+
// 2) Inicializar rotator (asegura carpeta si opts.mkdir === true)
|
|
24
|
+
this.rotator = new fs_1.FileRotator(this.fs, opts.basePath, this.clock, pattern,
|
|
25
|
+
// Para "size" podemos desear un nombre base fijo; si no, que use el daily pattern.
|
|
26
|
+
/* defaultSeedName */ undefined,
|
|
27
|
+
/* ensureDir */ !!opts.mkdir);
|
|
28
|
+
// 3) Preparar casos de uso (rotación + append)
|
|
29
|
+
this.rotateUC = new use_cases_1.RotateIfNeededUseCase(this.policy, this.rotator, this.clock);
|
|
30
|
+
this.appendUC = new use_cases_1.AppendLogUseCase(this.serializer);
|
|
31
|
+
// 4) Abrir el stream inicial en modo append
|
|
32
|
+
const initialPath = this.rotator.getActiveFilePath(); // path según patrón/fecha
|
|
33
|
+
this.stream = this.fs.createWriteStream(initialPath, { flags: "a" });
|
|
34
|
+
}
|
|
35
|
+
// Persiste un log. Orquesta:
|
|
36
|
+
// - Verificar si se requiere rotación (RotateIfNeededUseCase).
|
|
37
|
+
// - Si hay rotación: cierra stream actual, abre uno nuevo y dispara hook.
|
|
38
|
+
// - Escribir línea con backpressure (AppendLogUseCase).
|
|
39
|
+
async save(log) {
|
|
40
|
+
if (!this.stream)
|
|
41
|
+
throw new Error("[FileSystemDatasource] stream no inicializado");
|
|
42
|
+
try {
|
|
43
|
+
// 1) Rotación condicional
|
|
44
|
+
const nextPath = await this.rotateUC.execute();
|
|
45
|
+
if (nextPath) {
|
|
46
|
+
// Cerrar el stream actual de forma segura
|
|
47
|
+
await this.dispose();
|
|
48
|
+
// Actualizar activePath y abrir nuevo stream en append
|
|
49
|
+
this.rotator.setActivePath(nextPath);
|
|
50
|
+
this.stream = this.fs.createWriteStream(nextPath, { flags: "a" });
|
|
51
|
+
// Hook onRotate (no bloqueante)
|
|
52
|
+
Promise.resolve(this.opts.onRotate?.(/* old */ "", /* new */ nextPath)).catch((e) => this.opts.onError?.(e));
|
|
53
|
+
}
|
|
54
|
+
// 2) Append seguro con manejo de backpressure
|
|
55
|
+
await this.appendUC.execute({ log }, this.stream);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Notificar error (si hay handler) sin tumbar el proceso
|
|
59
|
+
await Promise.resolve(this.opts.onError?.(err));
|
|
60
|
+
// Re-lanzar si prefieres que el core lo capture:
|
|
61
|
+
// throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Espera a que el stream actual drene, si es necesario (útil antes de apagar).
|
|
65
|
+
async flush() {
|
|
66
|
+
if (!this.stream)
|
|
67
|
+
return;
|
|
68
|
+
const needDrain = this.stream.writableNeedDrain;
|
|
69
|
+
if (!needDrain)
|
|
70
|
+
return;
|
|
71
|
+
await new Promise((resolve) => this.stream.once("drain", resolve));
|
|
72
|
+
}
|
|
73
|
+
// Cierra el stream activo. Se usa al rotar y en shutdown.
|
|
74
|
+
async dispose() {
|
|
75
|
+
if (!this.stream)
|
|
76
|
+
return;
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
this.stream.end(() => resolve()); // end() drena y cierra el descriptor
|
|
79
|
+
this.stream.once("error", reject); // Por si ocurre un error al cerrar
|
|
80
|
+
});
|
|
81
|
+
this.stream = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.FileSystemDatasource = FileSystemDatasource;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./fs.datasource";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./fs.datasource"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IFsProvider } from ".";
|
|
2
|
+
import type { IClock, IFileRotatorPort } from "../../domain/contracts";
|
|
3
|
+
import { FileNamePattern } from "../../domain/value-objects";
|
|
4
|
+
export declare class FileRotatorAdapter implements IFileRotatorPort {
|
|
5
|
+
private readonly fs;
|
|
6
|
+
private readonly basePath;
|
|
7
|
+
private readonly clock;
|
|
8
|
+
private readonly pattern;
|
|
9
|
+
private readonly seedName?;
|
|
10
|
+
private activePath;
|
|
11
|
+
constructor(fs: IFsProvider, // Acceso a fs
|
|
12
|
+
basePath: string, // Carpeta base de logs
|
|
13
|
+
clock: IClock, // Reloj (testable)
|
|
14
|
+
pattern: FileNamePattern, // Patrón de nombre
|
|
15
|
+
ensureDir: boolean, // Crear carpeta si no existe
|
|
16
|
+
seedName?: string | undefined);
|
|
17
|
+
getActiveFilePath(): string;
|
|
18
|
+
setActivePath(next: string): void;
|
|
19
|
+
expectedPathForToday(now: Date): string;
|
|
20
|
+
sizeOf(path: string): Promise<number | null>;
|
|
21
|
+
nextIndexedPath(): Promise<string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileRotatorAdapter = void 0;
|
|
4
|
+
const _1 = require(".");
|
|
5
|
+
class FileRotatorAdapter {
|
|
6
|
+
constructor(fs, // Acceso a fs
|
|
7
|
+
basePath, // Carpeta base de logs
|
|
8
|
+
clock, // Reloj (testable)
|
|
9
|
+
pattern, // Patrón de nombre
|
|
10
|
+
ensureDir, // Crear carpeta si no existe
|
|
11
|
+
seedName // Nombre semilla para modo "size" (opcional)
|
|
12
|
+
) {
|
|
13
|
+
this.fs = fs;
|
|
14
|
+
this.basePath = basePath;
|
|
15
|
+
this.clock = clock;
|
|
16
|
+
this.pattern = pattern;
|
|
17
|
+
this.seedName = seedName;
|
|
18
|
+
if (ensureDir)
|
|
19
|
+
this.fs.mkdirSync(this.basePath, { recursive: true });
|
|
20
|
+
// Arrancamos con el path esperado para "hoy"
|
|
21
|
+
this.activePath = this.expectedPathForToday(this.clock.now());
|
|
22
|
+
}
|
|
23
|
+
getActiveFilePath() {
|
|
24
|
+
return this.activePath;
|
|
25
|
+
}
|
|
26
|
+
setActivePath(next) {
|
|
27
|
+
this.activePath = next;
|
|
28
|
+
}
|
|
29
|
+
expectedPathForToday(now) {
|
|
30
|
+
const fname = (0, _1.formatPattern)(this.pattern.pattern, now);
|
|
31
|
+
return (0, _1.joinBase)(this.basePath, fname);
|
|
32
|
+
}
|
|
33
|
+
async sizeOf(path) {
|
|
34
|
+
const stat = await this.fs.statSafe(path);
|
|
35
|
+
return stat ? stat.size : null;
|
|
36
|
+
}
|
|
37
|
+
async nextIndexedPath() {
|
|
38
|
+
// Si hay seedName (ej. "app.log") úsalo; si no, usa pattern de hoy
|
|
39
|
+
const seed = this.seedName ?? (0, _1.formatPattern)(this.pattern.pattern, this.clock.now());
|
|
40
|
+
const { base, ext } = (0, _1.splitBaseExt)(seed);
|
|
41
|
+
let i = 1;
|
|
42
|
+
while (true) {
|
|
43
|
+
const candidate = (0, _1.joinBase)(this.basePath, `${base}.${i}${ext}`);
|
|
44
|
+
const exists = await this.fs.exists(candidate);
|
|
45
|
+
if (!exists)
|
|
46
|
+
return candidate;
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.FileRotatorAdapter = FileRotatorAdapter;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
type CreateWriteStreamOpts = Parameters<typeof fs.createWriteStream>[1];
|
|
3
|
+
type WriteStreamType = ReturnType<typeof fs.createWriteStream>;
|
|
4
|
+
export interface IFsProvider {
|
|
5
|
+
createWriteStream(path: string, opts: CreateWriteStreamOpts): WriteStreamType;
|
|
6
|
+
mkdirSync(path: string, opts?: fs.MakeDirectoryOptions & {
|
|
7
|
+
recursive?: boolean;
|
|
8
|
+
}): void;
|
|
9
|
+
exists(path: string): Promise<boolean>;
|
|
10
|
+
statSafe(path: string): Promise<{
|
|
11
|
+
size: number;
|
|
12
|
+
} | null>;
|
|
13
|
+
}
|
|
14
|
+
export declare function createFsProvider(): IFsProvider;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createFsProvider = createFsProvider;
|
|
37
|
+
// Provider de fs compatible (tipos derivados para evitar errores por versión).
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const fsp = __importStar(require("fs/promises"));
|
|
40
|
+
function createFsProvider() {
|
|
41
|
+
return {
|
|
42
|
+
createWriteStream: (p, opts) => fs.createWriteStream(p, opts),
|
|
43
|
+
mkdirSync: (p, opts) => fs.mkdirSync(p, { recursive: true, ...(opts ?? {}) }),
|
|
44
|
+
exists: async (p) => await new Promise((r) => fs.access(p, (e) => r(!e))),
|
|
45
|
+
statSafe: async (p) => {
|
|
46
|
+
try {
|
|
47
|
+
const s = await fsp.stat(p);
|
|
48
|
+
return { size: s.size };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|