Skip to content

0014. Connettore dedicato per i file dei workspace Noeva

  • Stato: Accettata
  • Data: 2026-07-08
  • Decisori: Team BI/ETL

Contesto

Serve un connettore per leggere/scrivere file sui workspace del prodotto Noeva (via noeva-server-api): risoluzione per id, per nome esatto, per ricerca fuzzy; upload con overwrite o nuova versione. Lo StorageProvider esistente (src/connectors/file/) è path-based (list/get/put) e non modella queste capacità — id/nome/fuzzy e overwrite/versione sono concetti del dominio "file di un workspace Noeva", non dello storage a byte generico.

L'API (noeva-server-api) espone:

  • GET /api/files/:fileId — metadati by id (envelope { statusCode, message, payload }).
  • GET /api/files/search?name= — solo substring ILIKE server-side, nessun ranking fuzzy (envelope { success, data: { files, total_count } }diverso da quello sopra).
  • GET /api/files/:fileId/presigned-url — URL presigned per i byte originali (non/:fileId/content, che restituisce testo estratto/parsato, non i byte grezzi).
  • POST /api/files/upload/prepare + POST /api/files/upload/complete/:fileId — upload a due fasi; i booleani overwrite/version decidono sovrascrittura, nuova versione, o (nessuno dei due, su collisione nome) 409.
  • Nessun campo di checksum/hash esposto: per verificare se una nuova versione è identica alla corrente serve scaricare e hashare i byte lato client.

Decisione

  1. Famiglia di connettore dedicata (src/connectors/noeva/), sul modello di postgres/sqlserver (opzioni dedicate, client iniettato), non un'estensione dello StorageProvider: le operazioni (risoluzione per id/nome/fuzzy, overwrite/versione) non sono espresse dal contratto path-based list/get/put.
  2. Risoluzione a singolo best-match: noevaFileSource con fileName/fuzzyQuery ritorna un solo file (sopra soglia) o lancia un errore esplicito se ambiguo/assente — non una lista da disambiguare a valle. Più semplice da consumare in pipeline batch.
  3. Ranking fuzzy client-side con fuse.js: l'API fa solo ILIKE substring, nessuna fuzzy search server-side. fuse.js è isolato in fuzzy.ts (resolveBestMatch), sostituibile senza toccare source.ts/sink.ts se il ranking risultasse insoddisfacente.
  4. Idempotenza upload "nuova versione" via hash-compare: prima di caricare, se esiste già un file con lo stesso nome, noevaFileSink scarica la versione corrente e confronta sha256 con il nuovo contenuto; se identico, salta l'upload (written: 0). Evita versioni duplicate quando una pipeline viene rieseguita con lo stesso input.
  5. NOEVA_API_KEY deve essere una API key service-role del workspace, non una per-user: files-search.controller.ts scopa la ricerca all'utente proprietario a meno che il flag isServiceRole sia vero. Un connettore ETL che deve poter risolvere/caricare qualunque file del workspace (non solo quelli di un singolo utente umano) richiede la visibilità service-role. Documentato in .env.example; da provisionare esplicitamente in fase di deploy (non è la stessa API key personale usata da un utente nell'app).
  6. Client HTTP nuovo (NoevaHttpClient), non un StorageProvider: nessun SDK TS riutilizzabile esiste nel monorepo per queste API. Modellato su noeva-mcps/noeva-mcp-core/src/core/client.ts — parsing tollerante dell'envelope (data ?? payload ?? raw), necessario perché l'API non ha un envelope uniforme tra endpoint diversi (confermato leggendo i controller: search usa { data }, upload/get usano { payload }).
  7. Validazione Zod delle risposte (fileMetaWireSchema, ecc. in client.ts): i dati esterni entrano come unknown e vengono ristretti, come da regola generale del repo.

Alternative considerate

  • Estendere StorageProvider con metodi opzionali resolveByName/resolveFuzzy — scartata: avrebbe reso l'interfaccia generica dipendente da concetti (fuzzy, versioning) che solo Noeva ha, complicando filesystem/s3/sftp senza reale beneficio.
  • Fuzzy ranking multiplo (lista di candidati) invece di singolo best-match — scartata per lo scope attuale (uso in pipeline batch, non interattivo): un solo risultato o un errore esplicito tengono la pipeline deterministica.
  • Nessuna idempotenza (upload incondizionato in new-version) — scartata: avrebbe prodotto versioni duplicate a ogni retry/riesecuzione della pipeline con lo stesso input, violando la regola generale "rieseguire la pipeline non duplica dati".

Conseguenze

  • Positive: risoluzione file per id/nome/fuzzy e upload overwrite/nuova-versione disponibili come Source<NoevaFileContent>/Sink<Uint8Array> standard, componibili con i FileCodec esistenti a valle/monte per decodifica/codifica del contenuto.
  • Costi/limiti:
    • Nuova dipendenza fuse.js; isolata in fuzzy.ts.
    • Hash-compare scarica per intero la versione corrente prima di ogni upload new-version: costoso per file grandi (accettabile per i volumi ETL previsti; stesso limite ~50MB/no-TUS già noto in noeva-mcp-core).
    • Richiede il provisioning di una API key service-role in ogni ambiente (dev/staging/ prod) — un requisito di deploy in più rispetto a una chiave utente qualunque.
    • source.ts/sink.ts restano esclusi dalla soglia di coverage (glob esistente in vitest.config.ts), ma — a differenza di postgres/sqlserver — hanno comunque test unitari colocati con un NoevaHttpClient fake, essendo il client una nostra astrazione iniettabile e non un driver vendor.

Noeva è un marchio registrato di 4D S.R.L.