0015. Storage FTP/FTPS basato su basic-ftp
- Stato: Accettata
- Data: 2026-07-09
- Decisori: Team BI/ETL
Contesto
Alcuni gestionali depositano i file di scambio via FTP/FTPS invece di SFTP. Serve uno StorageProvider che si componga con i codec del File connector (vedi ADR-0007): elenco directory, download/upload in streaming, supporto sia a FTP in chiaro sia a FTPS. L' ADR-0011 aveva già scartato basic-ftp per SFTP (protocollo diverso) rimandando la scelta a questa issue (#30).
Decisione
Adottiamo basic-ftp (client FTP/FTPS puro JS, nessuna dipendenza nativa).
list(prefix): elenca i file della directoryprefix(non ricorsivo), filtrando suFileInfo.isFile.get: download in streaming verso unPassThrough(come SFTP), nessun file intero in memoria.put: upload in streaming (Readable.from(data)), creando le directory mancanti (ensureDir). A differenza dissh2-sftp-client.mkdir,ensureDirdibasic-ftpcambia la working directory del client verso la dir creata; per non lasciare stato residuo a sorpresa su un client iniettato e riusato,putnaviga sempre in modo esplicito (cd("/")prima e dopo l'operazione) invece di affidarsi alla cwd lasciata da una chiamata precedente.- FTPS opt-in esplicito:
secureèfalsedi default (FTP in chiaro),trueper FTPS esplicita (AUTH TLS),"implicit"per FTPS implicita (legacy). Un default "sicuro a sorpresa" romperebbe l'uso immediato con i gestionali FTP legacy che restano il caso d'uso più comune per questo connettore. - Il client è iniettato (DI, come gli altri storage):
loadFtpConfig(prefix)costruisce la config da env (FTP_HOST/PORT/USER/PASSWORD/SECURE). - Test: solo unit test, con un client
basic-ftpmockato (ftp.test.ts) che copre list/get/put e la gestione degli errori. A differenza di SFTP/S3, non c'è un test di integrazione reale in questo giro (nessun servizio Dockerftp-test): è una deviazione consapevole dal criterio di accettazione originale dell'issue, accettata per contenere lo scope. Il path I/O reale (incluso il negoziato TLS di FTPS) resta quindi verificato solo dal mock, non da un server reale.
Alternative considerate
ssh2-sftp-client— è per SFTP (SSH), non FTP/FTPS: protocollo diverso, non applicabile qui (vedi ADR-0011).- Client FTP nativi Node più datati (
jsftp,ftp) — meno mantenuti, supporto FTPS più debole o assente;basic-ftpè l'opzione moderna con supporto FTPS esplicita/implicita di prima classe e API a promesse. - Test di integrazione con server Docker reale (come
sftp-test) — scartata per questo giro per contenere lo scope; resta un'estensione naturale futura se emergono bug non catturabili col solo mock.
Conseguenze
- Positive: list/get/put FTP/FTPS con streaming, nessuna dipendenza nativa, API allineata allo
StorageProvideresistente (stesso modello di SFTP/S3). - Costi/limiti:
listnon ricorsivo (coerente col resto dei connettori); nessuna copertura I/O reale (solo mock) finché non viene aggiunto un test di integrazione dedicato;putpaga il costo di duecdextra per operazione per garantire uno stato prevedibile del client condiviso.

