@jmlq/logger-plugin-fs 0.1.0-alpha.6 → 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.
Files changed (120) hide show
  1. package/README.md +165 -117
  2. package/architecture.md +426 -0
  3. package/dist/application/dto/index.d.ts +2 -0
  4. package/dist/application/dto/index.js +2 -0
  5. package/dist/application/dto/rotation-check.dto.d.ts +5 -0
  6. package/dist/application/dto/save-log.dto.d.ts +3 -3
  7. package/dist/application/dto/write-operation.dto.d.ts +5 -0
  8. package/dist/application/factory/create-fs-datasource.factory.d.ts +3 -0
  9. package/dist/application/factory/create-fs-datasource.factory.js +26 -0
  10. package/dist/application/factory/index.d.ts +1 -0
  11. package/dist/{presentation → application}/factory/index.js +1 -1
  12. package/dist/application/services/fs-datasource.service.d.ts +7 -5
  13. package/dist/application/services/fs-datasource.service.js +6 -9
  14. package/dist/application/use-cases/append-log.use-case.d.ts +12 -0
  15. package/dist/application/use-cases/append-log.use-case.js +26 -0
  16. package/dist/application/use-cases/ensure-directory.use-case.d.ts +11 -0
  17. package/dist/application/use-cases/ensure-directory.use-case.js +27 -0
  18. package/dist/application/use-cases/index.d.ts +4 -3
  19. package/dist/application/use-cases/index.js +4 -3
  20. package/dist/application/use-cases/persist-log.use-case.d.ts +19 -0
  21. package/dist/application/use-cases/persist-log.use-case.js +47 -0
  22. package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +17 -0
  23. package/dist/application/use-cases/rotate-if-needed.use-case.js +28 -0
  24. package/dist/domain/ports/clock.port.d.ts +8 -0
  25. package/dist/domain/ports/file-path-adapter.port.d.ts +33 -0
  26. package/dist/domain/ports/fs-provider.port.d.ts +61 -0
  27. package/dist/domain/ports/fs-provider.port.js +2 -0
  28. package/dist/domain/ports/index.d.ts +4 -0
  29. package/dist/domain/{contracts → ports}/index.js +3 -3
  30. package/dist/domain/ports/stream-writer.port.d.ts +42 -0
  31. package/dist/domain/ports/stream-writer.port.js +2 -0
  32. package/dist/domain/value-objects/file-path.vo.d.ts +27 -0
  33. package/dist/domain/value-objects/file-path.vo.js +59 -0
  34. package/dist/domain/value-objects/file-size.vo.d.ts +14 -0
  35. package/dist/domain/value-objects/file-size.vo.js +57 -0
  36. package/dist/domain/value-objects/index.d.ts +2 -2
  37. package/dist/domain/value-objects/index.js +2 -2
  38. package/dist/index.d.ts +4 -3
  39. package/dist/index.js +14 -5
  40. package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +20 -0
  41. package/dist/infrastructure/adapters/file-rotator.adapter.js +105 -0
  42. package/dist/infrastructure/adapters/fs-provider.adapter.d.ts +17 -0
  43. package/dist/infrastructure/{fs/fs-provider.js → adapters/fs-provider.adapter.js} +41 -19
  44. package/dist/infrastructure/adapters/fs-writer.adapter.d.ts +13 -0
  45. package/dist/infrastructure/adapters/fs-writer.adapter.js +71 -0
  46. package/dist/infrastructure/{fs → adapters}/index.d.ts +2 -2
  47. package/dist/infrastructure/{fs → adapters}/index.js +2 -2
  48. package/dist/infrastructure/adapters/node-clock.adapter.d.ts +4 -0
  49. package/dist/infrastructure/{fs → adapters}/node-clock.adapter.js +0 -1
  50. package/dist/infrastructure/adapters/node-file-path.adapter.d.ts +16 -0
  51. package/dist/infrastructure/adapters/node-file-path.adapter.js +117 -0
  52. package/dist/infrastructure/filesystem/index.d.ts +4 -0
  53. package/dist/infrastructure/filesystem/index.js +20 -0
  54. package/dist/infrastructure/filesystem/polices/index.d.ts +1 -0
  55. package/dist/infrastructure/filesystem/polices/index.js +5 -0
  56. package/dist/infrastructure/filesystem/polices/rotation-policy.d.ts +29 -0
  57. package/dist/infrastructure/filesystem/polices/rotation-policy.js +55 -0
  58. package/dist/infrastructure/filesystem/ports/file-rotator.port.d.ts +32 -0
  59. package/dist/infrastructure/filesystem/ports/file-rotator.port.js +2 -0
  60. package/dist/infrastructure/filesystem/ports/index.d.ts +1 -0
  61. package/dist/{domain/types → infrastructure/filesystem/ports}/index.js +1 -1
  62. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +49 -0
  63. package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
  64. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +19 -0
  65. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
  66. package/dist/infrastructure/filesystem/types/filesystem-serializer.type.d.ts +10 -0
  67. package/dist/infrastructure/filesystem/types/filesystem-serializer.type.js +2 -0
  68. package/dist/infrastructure/filesystem/types/index.d.ts +3 -0
  69. package/dist/infrastructure/filesystem/types/index.js +19 -0
  70. package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.d.ts +22 -0
  71. package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.js +37 -0
  72. package/dist/infrastructure/filesystem/value-objects/index.d.ts +1 -0
  73. package/dist/infrastructure/{datasources → filesystem/value-objects}/index.js +1 -1
  74. package/dist/shared/errors/file-operation.error.d.ts +12 -0
  75. package/dist/shared/errors/file-operation.error.js +32 -0
  76. package/dist/shared/errors/fs-plugin.error.d.ts +4 -0
  77. package/dist/shared/errors/fs-plugin.error.js +11 -0
  78. package/dist/shared/errors/index.d.ts +3 -0
  79. package/dist/shared/errors/index.js +19 -0
  80. package/dist/shared/errors/rotation.error.d.ts +10 -0
  81. package/dist/shared/errors/rotation.error.js +25 -0
  82. package/install.md +520 -0
  83. package/package.json +39 -22
  84. package/LICENSE +0 -21
  85. package/dist/application/use-cases/append-log.usecase.d.ts +0 -7
  86. package/dist/application/use-cases/append-log.usecase.js +0 -19
  87. package/dist/application/use-cases/persist-log.usecase.d.ts +0 -23
  88. package/dist/application/use-cases/persist-log.usecase.js +0 -74
  89. package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +0 -10
  90. package/dist/application/use-cases/rotate-if-needed.usecase.js +0 -39
  91. package/dist/domain/contracts/clock.contract.d.ts +0 -3
  92. package/dist/domain/contracts/file-rotator.contract.d.ts +0 -7
  93. package/dist/domain/contracts/index.d.ts +0 -4
  94. package/dist/domain/contracts/serializer.contract.d.ts +0 -3
  95. package/dist/domain/contracts/stream-writer.port.d.ts +0 -6
  96. package/dist/domain/types/index.d.ts +0 -1
  97. package/dist/domain/types/options.type.d.ts +0 -11
  98. package/dist/domain/types/options.type.js +0 -5
  99. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +0 -4
  100. package/dist/domain/value-objects/file-name-pattern.vo.js +0 -14
  101. package/dist/domain/value-objects/rotation-policy.vo.d.ts +0 -7
  102. package/dist/domain/value-objects/rotation-policy.vo.js +0 -20
  103. package/dist/infrastructure/datasources/fs.datasource.d.ts +0 -17
  104. package/dist/infrastructure/datasources/fs.datasource.js +0 -84
  105. package/dist/infrastructure/datasources/index.d.ts +0 -1
  106. package/dist/infrastructure/fs/file-rotator.adapter.d.ts +0 -22
  107. package/dist/infrastructure/fs/file-rotator.adapter.js +0 -51
  108. package/dist/infrastructure/fs/fs-provider.d.ts +0 -15
  109. package/dist/infrastructure/fs/fs-writer.adapter.d.ts +0 -10
  110. package/dist/infrastructure/fs/fs-writer.adapter.js +0 -26
  111. package/dist/infrastructure/fs/node-clock.adapter.d.ts +0 -4
  112. package/dist/infrastructure/fs/path-utils.d.ts +0 -6
  113. package/dist/infrastructure/fs/path-utils.js +0 -26
  114. package/dist/presentation/factory/create-fs-datasource.d.ts +0 -15
  115. package/dist/presentation/factory/create-fs-datasource.js +0 -39
  116. package/dist/presentation/factory/index.d.ts +0 -1
  117. /package/dist/{domain/contracts/clock.contract.js → application/dto/rotation-check.dto.js} +0 -0
  118. /package/dist/{domain/contracts/file-rotator.contract.js → application/dto/write-operation.dto.js} +0 -0
  119. /package/dist/domain/{contracts/serializer.contract.js → ports/clock.port.js} +0 -0
  120. /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
- "version": "0.1.0-alpha.6",
4
- "author": "MLahuasi",
5
- "license": "MIT",
3
+ "description": "Filesystem plugin for JMLQ Logger implementing Clean Architecture principles.",
4
+ "version": "0.1.0-alpha.7",
6
5
  "main": "dist/index.js",
7
6
  "types": "dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
7
  "scripts": {
12
- "build": "tsc -p tsconfig.json",
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:cov": "jest --coverage"
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
- "@types/node": "^24.3.0",
20
- "tsx": "^4.20.5",
21
- "typescript": "^5.9.2",
22
- "@swc/core": "^1.13.5",
23
- "@swc/jest": "^0.2.39",
24
- "@types/jest": "^30.0.0",
25
- "jest": "^30.1.3"
26
- },
27
- "dependencies": {
28
- "@jmlq/logger": "^0.1.0-alpha.6"
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"
29
42
  },
30
- "overrides": {
31
- "test-exclude": "^7.0.1",
32
- "glob": "^10.4.5",
33
- "minimatch": "^9.0.5"
43
+ "files": [
44
+ "dist",
45
+ "README.md",
46
+ "architecture.md",
47
+ "install.md"
48
+ ],
49
+ "peerDependencies": {
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,3 +0,0 @@
1
- export interface IClock {
2
- now(): Date;
3
- }
@@ -1,7 +0,0 @@
1
- export interface IFileRotatorPort {
2
- getActiveFilePath(): string;
3
- expectedPathForToday(now: Date): string;
4
- sizeOf(path: string): Promise<number | null>;
5
- nextIndexedPath(): Promise<string>;
6
- setActivePath(next: string): void;
7
- }
@@ -1,4 +0,0 @@
1
- export * from "./clock.contract";
2
- export * from "./serializer.contract";
3
- export * from "./file-rotator.contract";
4
- export * from "./stream-writer.port";
@@ -1,3 +0,0 @@
1
- export interface IFsSerializer {
2
- serialize(entry: unknown): string;
3
- }
@@ -1,6 +0,0 @@
1
- export interface IStreamWriterPort {
2
- write(line: string): boolean;
3
- once(event: "drain" | "error", cb: (...a: any[]) => void): void;
4
- end(cb: () => void): void;
5
- readonly writableNeedDrain?: boolean;
6
- }
@@ -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,4 +0,0 @@
1
- export declare class FileNamePattern {
2
- readonly pattern: string;
3
- constructor(pattern: string);
4
- }
@@ -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,4 +0,0 @@
1
- import { IClock } from "../../domain/contracts";
2
- export declare class NodeClockAdapter implements IClock {
3
- now(): Date;
4
- }
@@ -1,6 +0,0 @@
1
- export declare function formatPattern(pattern: string, now: Date): string;
2
- export declare function splitBaseExt(fileName: string): {
3
- base: string;
4
- ext: string;
5
- };
6
- export declare function joinBase(basePath: string, fileName: string): string;