@damourlabs/euro-clinical-trials
Version:
A Nuxt 3 application for visualizing and managing clinical trials data in Europe, built with modern web technologies.
254 lines (210 loc) • 7.08 kB
text/typescript
// ~/stores/base-entity.ts
import { defineStore } from 'pinia';
import type { BaseRepositoryFilters, ResourceRepository } from '~/repositories/ResourceRepository';
interface Entity {
uuid: string | number
}
export type EntityStore = ReturnType<typeof createEntityStore>;
export function createEntityStore<
T extends Entity,
R extends ResourceRepository<T>
>(
storeName: string,
repository: R
) {
return defineStore(storeName, () => {
const items = ref<T[]>();
const loading = ref(false);
const error = ref<string>();
const getById = (id: string | number): T | undefined => {
if (!items.value) return undefined;
return items.value.find(item => item.uuid === id);
}
const isLoading = computed(() => loading)
const hasError = computed(() => !!error)
const isEmpty = computed(() => !loading && (!items.value || items.value.length === 0))
const hasItems = computed(() => items.value ? items.value.length > 0 : false)
const fetchAll = async (filters?: BaseRepositoryFilters) => {
loading.value = true
error.value = undefined
try {
// Set filters in the repository if provided
if (filters !== undefined) {
const paginatedResponse = await repository.findWithFilters(filters)
items.value = paginatedResponse.items
} else {
items.value = await repository.findAll()
}
console.log(`Fetched ${items.value.length} items from repository`)
} catch (err: unknown) {
console.error('HERE Error fetching items:', err)
if (err instanceof Error) {
error.value = err.message || 'Failed to fetch items'
} else {
error.value = 'An unknown error occurred'
}
throw error
} finally {
loading.value = false
}
}
const fetchById = async (id: string | number) => {
loading.value = true
error.value = undefined
try {
const item = await repository.findById(id)
if (!items.value) {
items.value = []
}
const index = items.value.findIndex((i: T) => i.uuid === id)
if (index !== -1) {
items.value[index] = item
} else {
items.value.push(item)
}
return item
} catch (err: unknown) {
if (err instanceof Error) {
error.value = err.message || 'Failed to fetch item'
} else {
error.value = 'An unknown error occurred'
}
throw error
} finally {
loading.value = false
}
}
const create = async (item: Partial<T>): Promise<T> => {
loading.value = true
error.value = undefined
try {
// Generate a unique ID
item.uuid = crypto.randomUUID()
const newItem = await repository.create(item)
if (!items.value) {
items.value = []
}
items.value.unshift(newItem)
return newItem
} catch (err: unknown) {
if (err instanceof Error) {
error.value = err.message || 'Failed to create item'
} else {
error.value = 'An unknown error occurred'
}
throw error
} finally {
loading.value = false
}
}
const update = async (id: string | number, item: Partial<T>) => {
loading.value = true
error.value = undefined
try {
const updatedItem = await repository.update(id, item)
if (!items.value) {
throw createError('Items list is not initialized')
}
const index = items.value.findIndex((i: T) => i.uuid === id)
if (index !== -1) {
items.value[index] = updatedItem
}
return updatedItem
} catch (err: unknown) {
if (err instanceof Error) {
error.value = err.message || 'Failed to update item'
} else {
error.value = 'An unknown error occurred'
}
throw error
} finally {
loading.value = false
}
}
const remove = async (id: string | number) => {
console.log(`Attempting to delete item with ID: ${id}`)
loading.value = true
error.value = undefined
try {
await repository.delete(id)
if (!items.value) {
throw createError('Items list is not initialized')
}
console.log(`Deleting item with ID: ${id}`)
// Remove the item from the local items array
items.value = items.value.filter((item: T) => item.uuid !== id)
console.log(`Item with ID ${id} removed from local store`)
console.log(`New items list:`, items.value)
console.log(`Item with ID ${id} deleted successfully`)
} catch (err: unknown) {
if (err instanceof Error) {
error.value = err.message || 'Failed to delete item'
throw error
}
} finally {
loading.value = false
}
}
const clearError = () => {
error.value = undefined
}
const reset = () => {
items.value = []
loading.value = false
error.value = undefined
}
// We want to expose any custom methods from the repository
const customMethods = getAllMethods(repository) as Omit<R, keyof ResourceRepository<T>>;
return {
items,
loading,
error,
isLoading,
hasError,
isEmpty,
hasItems,
getById,
customMethods,
fetchAll,
fetchById,
create,
update,
remove,
clearError,
reset
}
})
}
export type MethodNames<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];
// Base CRUD methods that should be excluded from custom methods
type BaseCrudMethods = 'findAll' | 'findById' | 'create' | 'update' | 'delete' | 'findWithFilters' | 'apiUrl' | 'resource';
// Get all method names except base CRUD methods
export type CustomMethodNames<T> = Exclude<MethodNames<T>, BaseCrudMethods>;
function getAllMethods<R extends ResourceRepository<Entity>>(classInstance: R): Record<string, (...args: unknown[]) => unknown> {
const methods: Record<string, (...args: unknown[]) => unknown> = {};
let currentPrototype = Object.getPrototypeOf(classInstance);
// Base CRUD methods to exclude
const baseMethods = new Set([
'findAll', 'findById', 'create', 'update', 'delete', 'findWithFilters',
'constructor', 'apiUrl', 'resource'
]);
while (currentPrototype && currentPrototype !== Object.prototype) {
const propertyNames = Object.getOwnPropertyNames(currentPrototype);
for (const name of propertyNames) {
if (
!baseMethods.has(name) &&
typeof classInstance[name as keyof R] === 'function' &&
!(name in methods)
) {
const method = classInstance[name as keyof R];
if (typeof method === 'function') {
methods[name] = method.bind(classInstance) as (...args: unknown[]) => unknown;
}
}
}
currentPrototype = Object.getPrototypeOf(currentPrototype);
}
return methods;
}