This commit is contained in:
David Arranz 2026-02-13 11:39:26 +01:00
parent 52ae21cb57
commit 770fb33bb0
18 changed files with 49 additions and 35 deletions

2
.vscode/launch.json vendored
View File

@ -1,5 +1,5 @@
{ {
"version": "0.4.7", "version": "0.4.8",
"configurations": [ "configurations": [
{ {
"name": "WEB: Vite (Chrome)", "name": "WEB: Vite (Chrome)",

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges-server", "name": "@erp/factuges-server",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsup src/index.ts --config tsup.config.ts", "build": "tsup src/index.ts --config tsup.config.ts",

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/factuges-web", "name": "@erp/factuges-web",
"private": true, "private": true,
"version": "0.4.7", "version": "0.4.8",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host --clearScreen false", "dev": "vite --host --clearScreen false",

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/auth", "name": "@erp/auth",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/core", "name": "@erp/core",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,13 @@
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
export class DocumentStorageKeyFactory { export class DocumentStorageKeyFactory {
static fromMetadataRecord(metadata: Record<string, unknown>): string { static fromMetadataRecord(metadata: Record<string, unknown>): {
return createHash("sha256").update(JSON.stringify(metadata)).digest("hex"); paths: string[];
storageKey: string;
} {
return {
paths: [String(metadata.companySlug), String(metadata.documentType)],
storageKey: createHash("sha256").update(JSON.stringify(metadata)).digest("hex"),
};
} }
} }

View File

@ -8,7 +8,7 @@ export interface IDocumentStorage {
* - Best-effort * - Best-effort
* - Nunca lanza (errores se gestionan internamente) * - Nunca lanza (errores se gestionan internamente)
*/ */
existsKeyStorage(storageKey: string): Promise<Boolean>; existsKeyStorage(storageKey: string, paths: string[]): Promise<Boolean>;
/** /**
* Recupera un documento guardado. * Recupera un documento guardado.
@ -17,7 +17,7 @@ export interface IDocumentStorage {
* - Best-effort * - Best-effort
* - Nunca lanza (errores se gestionan internamente) * - Nunca lanza (errores se gestionan internamente)
*/ */
readDocument(storageKey: string): Promise<IDocument | null>; readDocument(storageKey: string, paths: string[]): Promise<IDocument | null>;
/** /**
* Persiste un documento generado. * Persiste un documento generado.

View File

@ -1,6 +1,8 @@
import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { buildSafePath } from "@repo/rdx-utils";
import { import {
DocumentStorageKeyFactory, DocumentStorageKeyFactory,
type IDocument, type IDocument,
@ -15,11 +17,15 @@ import {
* - No afecta al flujo del caso de uso * - No afecta al flujo del caso de uso
*/ */
export class FilesystemDocumentStorage implements IDocumentStorage { export class FilesystemDocumentStorage implements IDocumentStorage {
public constructor(private readonly basePath: string) {} public constructor(private readonly docRootPath: string) {}
async existsKeyStorage(storageKey: string, paths: string[]): Promise<Boolean> {
// Ejemplo: .../signed-documents/770c138fc58548f72029c1ed4f3670e94be4cc7a0fab9f7f0b84d0aef77f43ae
// paths: => [".../signed-documents"],
// storageKey: => 770c138fc58548f72029c1ed4f3670e94be4cc7a0fab9f7f0b84d0aef77f43ae
async existsKeyStorage(storageKey: string): Promise<Boolean> {
try { try {
const dir = this.resolveDirFromStorageKey(storageKey); const dir = this.resolveDir(storageKey, paths);
return (await stat(dir)).isDirectory(); return (await stat(dir)).isDirectory();
} catch { } catch {
// Consistente con saveDocument: best-effort // Consistente con saveDocument: best-effort
@ -27,12 +33,12 @@ export class FilesystemDocumentStorage implements IDocumentStorage {
} }
} }
async readDocument(storageKey: string) { async readDocument(storageKey: string, paths: string[]) {
try { try {
const dir = this.resolveDirFromStorageKey(storageKey); const dir = this.resolveDir(storageKey, paths);
const payload = await readFile(path.join(dir, "document.bin")); const payload = await readFile(path.join(dir, "document.bin"));
const metaRaw = JSON.parse(await readFile(path.join(dir, "document.meta.json"), "utf-8")); const metaRaw = await readFile(path.join(dir, "document.meta.json"), "utf-8");
const meta = JSON.parse(metaRaw) as { const meta = JSON.parse(metaRaw) as {
mimeType: string; mimeType: string;
@ -78,15 +84,15 @@ export class FilesystemDocumentStorage implements IDocumentStorage {
} }
private resolveDirFromMetadataRecord(metadataRecord: Record<string, unknown>): string { private resolveDirFromMetadataRecord(metadataRecord: Record<string, unknown>): string {
/** const { paths, storageKey } = DocumentStorageKeyFactory.fromMetadataRecord(metadataRecord);
* El storage NO decide claves semánticas.
* Se limita a generar un path técnico estable. return this.resolveDir(storageKey, paths);
*/
const storageKey = DocumentStorageKeyFactory.fromMetadataRecord(metadataRecord);
return this.resolveDirFromStorageKey(storageKey);
} }
private resolveDirFromStorageKey(storageKey: string): string { private resolveDir(storageKey: string, paths: string[]): string {
return path.join(this.basePath, storageKey); return buildSafePath({
basePath: this.docRootPath,
segments: [...paths, storageKey],
});
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customer-invoices", "name": "@erp/customer-invoices",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -21,23 +21,25 @@ export class IssuedInvoiceSignedDocumentCachePreProcessor implements IDocumentPr
async tryResolve(metadata: IDocumentMetadata): Promise<IDocument | null> { async tryResolve(metadata: IDocumentMetadata): Promise<IDocument | null> {
const metadataRecord = metadata as unknown as Record<string, unknown>; const metadataRecord = metadata as unknown as Record<string, unknown>;
try { try {
const storageKey = DocumentStorageKeyFactory.fromMetadataRecord(metadataRecord); const { paths, storageKey } = DocumentStorageKeyFactory.fromMetadataRecord(metadataRecord);
if (!storageKey) { if (!storageKey) {
return null; return null;
} }
const exists = await this.docStorage.existsKeyStorage(storageKey); const exists = await this.docStorage.existsKeyStorage(storageKey, paths);
if (!exists) { if (!exists) {
return null; return null;
} }
const document = await this.docStorage.readDocument(storageKey); logger.info(`✅ Found Server cached document for key ${storageKey}`);
const document = await this.docStorage.readDocument(storageKey, paths);
if (!this.isValid(document)) { if (!this.isValid(document)) {
logger.warn(`Storage key ${storageKey} not exists!`, { logger.warn(`Corrupted or invalid cached document for key ${storageKey}`, {
lable: "IssuedInvoiceSignedDocumentCachePreProcessor", label: "IssuedInvoiceSignedDocumentCachePreProcessor",
}); });
return null; return null;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customers", "name": "@erp/customers",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/doc-numbering", "name": "@erp/doc-numbering",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-criteria", "name": "@repo/rdx-criteria",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ddd", "name": "@repo/rdx-ddd",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-logger", "name": "@repo/rdx-logger",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-utils", "name": "@repo/rdx-utils",
"version": "0.4.7", "version": "0.4.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,