F7: Deduplicate the four CRUD composables

useExercises/useCombos/useSessions/useCollections were the same 54
lines with a different repository (~160 duplicated lines). They are now
one-liners over a createCrudComposable(repository, itemsKey) factory;
the returned API is identical so no call site changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David 2026-07-09 15:39:50 +02:00
parent a6b4723ac6
commit e3847edae1
5 changed files with 79 additions and 208 deletions

View file

@ -0,0 +1,71 @@
import { ref } from 'vue'
/**
* Factory for the four identical CRUD composables (exercises, combos,
* sessions, collections). Returns a composable exposing the reactive item
* list under `itemsKey` plus the shared loading/error/CRUD API, so
* useExercises & co. stay one-liners with unchanged call sites.
*/
export function createCrudComposable(repository, itemsKey) {
return function useCrud() {
const items = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchAll() {
loading.value = true
error.value = null
try {
items.value = await repository.getAll()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function create(data) {
const id = await repository.create(data)
await fetchAll()
return id
}
async function update(id, data) {
await repository.update(id, data)
await fetchAll()
}
async function remove(id) {
await repository.delete(id)
await fetchAll()
}
async function search(query) {
loading.value = true
error.value = null
try {
items.value = await repository.search(query)
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function getById(id) {
return repository.getById(id)
}
return {
[itemsKey]: items,
loading,
error,
fetchAll,
create,
update,
remove,
search,
getById,
}
}
}

View file

@ -1,54 +1,4 @@
import { ref } from 'vue'
import { collectionRepository } from '../db/repositories/collectionRepository.js'
import { createCrudComposable } from './createCrudComposable.js'
export function useCollections() {
const collections = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchAll() {
loading.value = true
error.value = null
try {
collections.value = await collectionRepository.getAll()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function create(data) {
const id = await collectionRepository.create(data)
await fetchAll()
return id
}
async function update(id, data) {
await collectionRepository.update(id, data)
await fetchAll()
}
async function remove(id) {
await collectionRepository.delete(id)
await fetchAll()
}
async function search(query) {
loading.value = true
error.value = null
try {
collections.value = await collectionRepository.search(query)
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function getById(id) {
return collectionRepository.getById(id)
}
return { collections, loading, error, fetchAll, create, update, remove, search, getById }
}
export const useCollections = createCrudComposable(collectionRepository, 'collections')

View file

@ -1,54 +1,4 @@
import { ref } from 'vue'
import { comboRepository } from '../db/repositories/comboRepository.js'
import { createCrudComposable } from './createCrudComposable.js'
export function useCombos() {
const combos = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchAll() {
loading.value = true
error.value = null
try {
combos.value = await comboRepository.getAll()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function create(data) {
const id = await comboRepository.create(data)
await fetchAll()
return id
}
async function update(id, data) {
await comboRepository.update(id, data)
await fetchAll()
}
async function remove(id) {
await comboRepository.delete(id)
await fetchAll()
}
async function search(query) {
loading.value = true
error.value = null
try {
combos.value = await comboRepository.search(query)
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function getById(id) {
return comboRepository.getById(id)
}
return { combos, loading, error, fetchAll, create, update, remove, search, getById }
}
export const useCombos = createCrudComposable(comboRepository, 'combos')

View file

@ -1,54 +1,4 @@
import { ref } from 'vue'
import { exerciseRepository } from '../db/repositories/exerciseRepository.js'
import { createCrudComposable } from './createCrudComposable.js'
export function useExercises() {
const exercises = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchAll() {
loading.value = true
error.value = null
try {
exercises.value = await exerciseRepository.getAll()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function create(data) {
const id = await exerciseRepository.create(data)
await fetchAll()
return id
}
async function update(id, data) {
await exerciseRepository.update(id, data)
await fetchAll()
}
async function remove(id) {
await exerciseRepository.delete(id)
await fetchAll()
}
async function search(query) {
loading.value = true
error.value = null
try {
exercises.value = await exerciseRepository.search(query)
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function getById(id) {
return exerciseRepository.getById(id)
}
return { exercises, loading, error, fetchAll, create, update, remove, search, getById }
}
export const useExercises = createCrudComposable(exerciseRepository, 'exercises')

View file

@ -1,54 +1,4 @@
import { ref } from 'vue'
import { sessionRepository } from '../db/repositories/sessionRepository.js'
import { createCrudComposable } from './createCrudComposable.js'
export function useSessions() {
const sessions = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchAll() {
loading.value = true
error.value = null
try {
sessions.value = await sessionRepository.getAll()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function create(data) {
const id = await sessionRepository.create(data)
await fetchAll()
return id
}
async function update(id, data) {
await sessionRepository.update(id, data)
await fetchAll()
}
async function remove(id) {
await sessionRepository.delete(id)
await fetchAll()
}
async function search(query) {
loading.value = true
error.value = null
try {
sessions.value = await sessionRepository.search(query)
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
async function getById(id) {
return sessionRepository.getById(id)
}
return { sessions, loading, error, fetchAll, create, update, remove, search, getById }
}
export const useSessions = createCrudComposable(sessionRepository, 'sessions')