Client interfaces (DI)
Perché esistono
ImportExportApi è una classe concreta che parla con i microservizi node-excel-import / node-excel-export. Per usare la libreria con un backend diverso (SDK custom, GraphQL, mock di test, contratto wire alternativo) senza ereditare dalla classe, basta implementare le 3 interfacce esposte.
I componenti ImportWizard e ExportDialog accettano la prop api: ImportExportClient (l'unione delle 3 interfacce) — accetta quindi qualsiasi oggetto che soddisfi i contratti.
I 3 contratti
IImportClient
interface IImportClient {
uploadFile(file: File, risorsa: string, opzioniCsv?: Partial<CsvOpzioni>): Promise<UploadResult>
testImport(sessionId: string, mappings: MappingColonna[]): Promise<TestImportResult>
executeImport(sessionId: string, mappings: MappingColonna[]): Promise<ImportResult>
getImportStatus(importId: string): Promise<ImportProgresso>
uploadImages(sessionId: string, files: File[]): Promise<{ caricati: number; errori: string[] }>
downloadTemplate(risorsa: string, formato: string): Promise<Blob>
getImportHistory(risorsa?: string): Promise<ImportHistoryItem[]>
getImportFieldMetadata(risorsa: string): Promise<CampoMetadato[]>
cancelUpload(): void
}
Operazioni di import: upload, dry-run, esecuzione, polling status, immagini collegate, template d'esempio, storico, metadata campi destinazione.
IExportClient
interface IExportClient {
executeExport(request: ExportRequest): Promise<ServerAsyncExportPayload | Blob>
getExportStatus(exportId: string): Promise<ExportProgresso>
downloadExport(url: string): Promise<Blob>
getExportFieldMetadata(risorsa: string): Promise<CampoMetadato[]>
cancelExport(): void
}
Operazioni di export: esecuzione (sincrona/asincrona), polling, download del file generato, metadata campi esportabili.
ITemplateClient
interface ITemplateClient {
getTemplates(risorsa?: string): Promise<ModelloExport[]>
createTemplate(
template: Omit<ModelloExport, 'id' | 'dataCreazione' | 'dataModifica' | 'creatoDa'>
): Promise<ModelloExport>
updateTemplate(id: string, template: Partial<ModelloExport>): Promise<ModelloExport>
deleteTemplate(id: string): Promise<void>
}
CRUD dei template di export salvati (modelli riusabili con campi/filtri/ordinamento).
ImportExportClient (union)
type ImportExportClient = IImportClient & IExportClient & ITemplateClient
Tipo accettato da ImportWizard.api / ExportDialog.api / IMPORT_EXPORT_API_KEY. Le prop sui componenti smart richiedono l'unione completa: anche se un caso d'uso esercita solo le funzioni di import, l'oggetto deve esporre comunque tutti i 26 metodi per soddisfare il tipo.
as ImportExportClient un oggetto che implementa solo i metodi necessari. Vedi sezione Mock per test.Esempio: mock per test
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { ImportWizard, type ImportExportClient } from '@pzeta/vue-importexport'
const mockClient = {
// Solo i metodi che il test esercita
uploadFile: vi.fn().mockResolvedValue({
sessionId: 'sess-1',
righe: 100,
colonne: ['nome', 'cognome', 'email'],
}),
getImportFieldMetadata: vi.fn().mockResolvedValue([
{ nome: 'nome', etichetta: 'Nome', tipo: 'string', obbligatorio: true },
{ nome: 'cognome', etichetta: 'Cognome', tipo: 'string', obbligatorio: true },
{ nome: 'email', etichetta: 'Email', tipo: 'email', obbligatorio: false },
]),
testImport: vi.fn().mockResolvedValue({ righeValide: 98, righeErrore: 2, errori: [] }),
executeImport: vi.fn().mockResolvedValue({
stato: 'completato',
righeImportate: 98,
righeScartate: 2,
}),
cancelUpload: vi.fn(),
// I restanti metodi delle 3 interfacce non sono toccati nel test
} as unknown as ImportExportClient
it("esegue l'upload e il test", async () => {
const wrapper = mount(ImportWizard, {
props: {
visible: true,
baseUrl: 'http://localhost',
risorsa: 'anagrafica',
api: mockClient,
},
})
// ... interagisci col wizard
expect(mockClient.uploadFile).toHaveBeenCalled()
})
Esempio: backend GraphQL
Skeleton per backend GraphQL — i 3 contratti vengono implementati separatamente e composti:
import type {
IImportClient,
IExportClient,
ITemplateClient,
ImportExportClient,
} from '@pzeta/vue-importexport'
import type { GraphQLClient } from 'graphql-request'
class GraphQLImportClient implements IImportClient {
constructor(private readonly gql: GraphQLClient) {}
async uploadFile(file, risorsa, opzioniCsv) {
// GraphQL multipart upload spec
const { upload } = await this.gql.request(UPLOAD_MUTATION, { file, risorsa, opzioniCsv })
return upload
}
async testImport(sessionId, mappings) {
const { test } = await this.gql.request(TEST_MUTATION, { sessionId, mappings })
return test
}
async executeImport(sessionId, mappings) {
const { execute } = await this.gql.request(EXECUTE_MUTATION, { sessionId, mappings })
return execute
}
async getImportStatus(importId) {
const { status } = await this.gql.request(STATUS_QUERY, { importId })
return status
}
async uploadImages(sessionId, files) { /* ... */ }
async downloadTemplate(risorsa, formato) {
// GraphQL non gestisce binari: fallback a endpoint REST/CDN dedicato
const res = await fetch(`/api/import/template/${risorsa}?formato=${formato}`)
return res.blob()
}
async getImportHistory(risorsa) { /* ... */ }
async getImportFieldMetadata(risorsa) { /* ... */ }
cancelUpload() { /* GraphQL request abort */ }
}
class GraphQLExportClient implements IExportClient { /* ... */ }
class GraphQLTemplateClient implements ITemplateClient { /* ... */ }
// Composizione delle 3 implementazioni
function createGraphQLClient(gql: GraphQLClient): ImportExportClient {
const importer = new GraphQLImportClient(gql)
const exporter = new GraphQLExportClient(gql)
const templates = new GraphQLTemplateClient(gql)
return Object.assign({}, importer, exporter, templates) as ImportExportClient
}
const myClient = createGraphQLClient(myGraphQLClient)
<ImportWizard :api="myClient" base-url="" risorsa="anagrafica" />
downloadTemplate, downloadExport e executeExport (per Blob immediati) a endpoint REST/CDN dedicati.Esempio: estensione di ImportExportApi
Per estendere il client default (es. aggiungere caching, retry, header custom) puoi sottoclassarlo:
import { ImportExportApi, type ImportExportApiOptions } from '@pzeta/vue-importexport'
import type { CampoMetadato } from '@pzeta/vue-importexport'
class CachedImportExportApi extends ImportExportApi {
private metadataCache = new Map<string, CampoMetadato[]>()
constructor(baseUrl: string, options?: ImportExportApiOptions) {
super(baseUrl, options)
}
async getImportFieldMetadata(risorsa: string): Promise<CampoMetadato[]> {
if (this.metadataCache.has(risorsa)) {
return this.metadataCache.get(risorsa)!
}
const fields = await super.getImportFieldMetadata(risorsa)
this.metadataCache.set(risorsa, fields)
return fields
}
}
const api = new CachedImportExportApi(baseUrl)
<ImportWizard :api="api" :base-url="baseUrl" risorsa="anagrafica" />
Iniezione manuale via IMPORT_EXPORT_API_KEY
Quando usi i singoli step / composables fuori da ImportWizard/ExportDialog, fornisci il client via provide:
<script setup lang="ts">
import { provide } from 'vue'
import {
IMPORT_EXPORT_API_KEY,
ImportExportApi,
} from '@pzeta/vue-importexport'
const api = new ImportExportApi(import.meta.env.VITE_IMPORTEXPORT_API_URL)
provide(IMPORT_EXPORT_API_KEY, api)
</script>
<template>
<ImportStepUpload />
<ImportStepMapping />
<!-- ... -->
</template>
useApi() recupera l'oggetto provisto. Il tipo della key è InjectionKey<ImportExportClient> — quindi puoi fornire qualsiasi implementazione delle 3 interfacce, non solo la classe ImportExportApi.
TypeScript
Tutti i tipi rilevanti sono esportati dal package root:
import type {
IImportClient,
IExportClient,
ITemplateClient,
ImportExportClient,
ImportExportApiOptions,
} from '@pzeta/vue-importexport'
import {
ImportExportApi,
IMPORT_EXPORT_API_KEY,
} from '@pzeta/vue-importexport'