From 86bb8af32d232b768b35c2c6b4c8a9986939f03f Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 9 Feb 2026 11:13:20 +0100 Subject: [PATCH] refactor(api): extract shared extractCollection helper (F2.1) Create shared/utils/apiHelpers.ts with generic extractCollection() that handles hydra:member, member, items, data, and array formats. Replace 7 local implementations in CRUD composables. Co-Authored-By: Claude Opus 4.6 --- app/composables/useComposants.ts | 18 +----------------- app/composables/useConstructeurs.ts | 18 +----------------- app/composables/useDocuments.ts | 18 +----------------- app/composables/usePieces.ts | 18 +----------------- app/composables/useProducts.ts | 18 +----------------- app/composables/useSites.ts | 20 +------------------- app/shared/utils/apiHelpers.ts | 16 ++++++++++++++++ 7 files changed, 22 insertions(+), 104 deletions(-) create mode 100644 app/shared/utils/apiHelpers.ts diff --git a/app/composables/useComposants.ts b/app/composables/useComposants.ts index e9e9bce..be1d070 100644 --- a/app/composables/useComposants.ts +++ b/app/composables/useComposants.ts @@ -4,6 +4,7 @@ import { useApi } from './useApi' import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils' import { useConstructeurs, type Constructeur } from './useConstructeurs' import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Composant { id: string @@ -45,23 +46,6 @@ const composants = ref([]) const total = ref(0) const loading = ref(false) -const extractCollection = (payload: unknown): Composant[] => { - if (Array.isArray(payload)) { - return payload as Composant[] - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Composant[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Composant[] - } - if (Array.isArray(p?.data)) { - return p.data as Composant[] - } - return [] -} - const extractTotal = (payload: unknown, fallbackLength: number): number => { const p = payload as Record | null if (typeof p?.totalItems === 'number') { diff --git a/app/composables/useConstructeurs.ts b/app/composables/useConstructeurs.ts index 7da2c76..9e4f36c 100644 --- a/app/composables/useConstructeurs.ts +++ b/app/composables/useConstructeurs.ts @@ -1,6 +1,7 @@ import { ref } from 'vue' import { useApi } from './useApi' import { useToast } from './useToast' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Constructeur { id: string @@ -52,23 +53,6 @@ const upsertConstructeurs = (items: Constructeur[] = []) => { const getIndexedConstructeur = (id: string): Constructeur | null => constructeurs.value.find((item) => item && item.id === id) || null -const extractCollection = (payload: unknown): Constructeur[] => { - if (Array.isArray(payload)) { - return payload as Constructeur[] - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Constructeur[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Constructeur[] - } - if (Array.isArray(p?.data)) { - return p.data as Constructeur[] - } - return [] -} - const pendingFetches = new Map>() export function useConstructeurs() { diff --git a/app/composables/useDocuments.ts b/app/composables/useDocuments.ts index c513b12..b34e549 100644 --- a/app/composables/useDocuments.ts +++ b/app/composables/useDocuments.ts @@ -2,6 +2,7 @@ import { ref } from 'vue' import { useApi } from './useApi' import { useToast } from './useToast' import { normalizeRelationIds } from '~/shared/apiRelations' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Document { id: string @@ -34,23 +35,6 @@ export interface DocumentResult { const documents = ref([]) const loading = ref(false) -const extractCollection = (payload: unknown): Document[] => { - if (Array.isArray(payload)) { - return payload - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Document[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Document[] - } - if (Array.isArray(p?.data)) { - return p.data as Document[] - } - return [] -} - const fileToBase64 = (file: File): Promise => new Promise((resolve, reject) => { const reader = new FileReader() diff --git a/app/composables/usePieces.ts b/app/composables/usePieces.ts index 0ccab82..cd11e81 100644 --- a/app/composables/usePieces.ts +++ b/app/composables/usePieces.ts @@ -4,6 +4,7 @@ import { useApi } from './useApi' import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils' import { useConstructeurs, type Constructeur } from './useConstructeurs' import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Piece { id: string @@ -46,23 +47,6 @@ const pieces = ref([]) const total = ref(0) const loading = ref(false) -const extractCollection = (payload: unknown): Piece[] => { - if (Array.isArray(payload)) { - return payload as Piece[] - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Piece[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Piece[] - } - if (Array.isArray(p?.data)) { - return p.data as Piece[] - } - return [] -} - const extractTotal = (payload: unknown, fallbackLength: number): number => { const p = payload as Record | null if (typeof p?.totalItems === 'number') { diff --git a/app/composables/useProducts.ts b/app/composables/useProducts.ts index 065e501..07167a0 100644 --- a/app/composables/useProducts.ts +++ b/app/composables/useProducts.ts @@ -4,6 +4,7 @@ import { useApi } from './useApi' import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils' import { useConstructeurs, type Constructeur } from './useConstructeurs' import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Product { id: string @@ -62,23 +63,6 @@ const replaceInCache = (item: Product): boolean => { return false } -const extractCollection = (payload: unknown): Product[] => { - if (Array.isArray(payload)) { - return payload as Product[] - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Product[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Product[] - } - if (Array.isArray(p?.data)) { - return p.data as Product[] - } - return [] -} - const extractTotal = (payload: unknown, fallbackLength: number): number => { const p = payload as Record | null if (typeof p?.totalItems === 'number') { diff --git a/app/composables/useSites.ts b/app/composables/useSites.ts index 022ba56..b6e23bd 100644 --- a/app/composables/useSites.ts +++ b/app/composables/useSites.ts @@ -1,6 +1,7 @@ import { ref } from 'vue' import { useToast } from './useToast' import { useApi } from './useApi' +import { extractCollection } from '~/shared/utils/apiHelpers' export interface Site { id: string @@ -24,23 +25,6 @@ interface SiteResult { const sites = ref([]) const loading = ref(false) -const extractCollection = (payload: unknown): Site[] => { - if (Array.isArray(payload)) { - return payload as Site[] - } - const p = payload as Record | null - if (Array.isArray(p?.member)) { - return p.member as Site[] - } - if (Array.isArray(p?.['hydra:member'])) { - return p['hydra:member'] as Site[] - } - if (Array.isArray(p?.data)) { - return p.data as Site[] - } - return [] -} - export function useSites() { const { showSuccess, showInfo } = useToast() const { get, post, patch, delete: del } = useApi() @@ -49,8 +33,6 @@ export function useSites() { loading.value = true try { const result = await get('/sites') - console.log('sites api result', result) - if (result.success) { const collection = extractCollection(result.data) sites.value = collection diff --git a/app/shared/utils/apiHelpers.ts b/app/shared/utils/apiHelpers.ts new file mode 100644 index 0000000..54e9640 --- /dev/null +++ b/app/shared/utils/apiHelpers.ts @@ -0,0 +1,16 @@ +/** + * Shared API response helpers. + * + * Extracted from 10+ composables/components that each had an identical local + * copy of extractCollection (parsing hydra:member / member / data / array). + */ + +export function extractCollection(payload: unknown): T[] { + if (Array.isArray(payload)) return payload as T[] + const p = payload as Record | null + if (Array.isArray(p?.member)) return p!.member as T[] + if (Array.isArray(p?.['hydra:member'])) return p!['hydra:member'] as T[] + if (Array.isArray(p?.items)) return p!.items as T[] + if (Array.isArray(p?.data)) return p!.data as T[] + return [] +}