Derive client domain types from the shared schema contracts

Add entity/response Zod schemas to @trek/shared (place, trip, assignment, day, budget, packing, reservation), each matched against the producing server service, and re-export them from client types.ts instead of the hand-written duplicates that had drifted (name/title, amount/total_price, owner_id/user_id, cover_url/cover_image, ...). Updates the call sites and test fixtures the corrected types surfaced; type-only, no runtime behaviour change.
This commit is contained in:
Maurice
2026-05-31 15:42:39 +02:00
parent 239a68bb48
commit 3977a5ecba
52 changed files with 732 additions and 435 deletions
+24 -13
View File
@@ -66,14 +66,15 @@ export function buildTrip(overrides: Partial<Trip> = {}): Trip {
const id = next();
return {
id,
name: `Trip ${id}`,
user_id: 1,
title: `Trip ${id}`,
description: null,
start_date: '2025-06-01',
end_date: '2025-06-05',
cover_url: null,
is_archived: false,
currency: 'EUR',
cover_image: null,
is_archived: 0,
reminder_days: 7,
owner_id: 1,
created_at: '2025-01-01T00:00:00.000Z',
updated_at: '2025-01-01T00:00:00.000Z',
...overrides,
@@ -105,14 +106,19 @@ export function buildPlace(overrides: Partial<Place> = {}): Place {
lng: 2.3522,
address: null,
category_id: null,
icon: null,
price: null,
currency: null,
image_url: null,
google_place_id: null,
osm_id: null,
route_geometry: null,
place_time: null,
end_time: null,
duration_minutes: 60,
notes: null,
transport_mode: 'walking',
website: null,
phone: null,
created_at: '2025-01-01T00:00:00.000Z',
...overrides,
};
@@ -154,6 +160,7 @@ export function buildPackingItem(overrides: Partial<PackingItem> = {}): PackingI
name: `Packing item ${id}`,
category: null,
checked: 0,
sort_order: 0,
quantity: 1,
...overrides,
};
@@ -181,14 +188,16 @@ export function buildBudgetItem(overrides: Partial<BudgetItem> = {}): BudgetItem
return {
id,
trip_id: 1,
category: 'Other',
name: `Budget item ${id}`,
amount: 100,
currency: 'EUR',
category: null,
paid_by: null,
total_price: 100,
persons: 1,
days: null,
note: null,
sort_order: 0,
members: [],
expense_date: null,
created_at: '2025-01-01T00:00:00.000Z',
...overrides,
};
}
@@ -198,14 +207,14 @@ export function buildReservation(overrides: Partial<Reservation> = {}): Reservat
return {
id,
trip_id: 1,
name: `Reservation ${id}`,
title: `Reservation ${id}`,
type: 'restaurant',
status: 'confirmed',
date: null,
time: null,
reservation_time: null,
reservation_end_time: null,
location: null,
confirmation_number: null,
notes: null,
url: null,
created_at: '2025-01-01T00:00:00.000Z',
...overrides,
};
@@ -219,6 +228,7 @@ export function buildTripFile(overrides: Partial<TripFile> = {}): TripFile {
filename: 'test.pdf',
original_name: 'test.pdf',
mime_type: 'application/pdf',
url: `/api/trips/1/files/${id}/download`,
created_at: '2025-01-01T00:00:00.000Z',
...overrides,
};
@@ -240,6 +250,7 @@ export function buildCategory(overrides: Partial<Category> = {}): Category {
return {
id,
name: `Category ${id}`,
color: '#6366f1',
icon: 'restaurant',
user_id: 1,
...overrides,
+10 -2
View File
@@ -25,9 +25,17 @@ export function resetAllStores(): void {
usePermissionsStore.setState(initialPermsState, true);
}
/**
* Tests routinely seed a store with a partially-populated slice of state,
* including partial nested objects (e.g. only `settings.time_format`). The
* store's own setState wants the exact field types, so seeding accepts a
* deep-partial view and casts at the boundary.
*/
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
export function seedStore<T extends object>(
store: { setState: (partial: Partial<T>, replace?: boolean) => void },
state: Partial<T>,
state: DeepPartial<T>,
): void {
store.setState(state);
store.setState(state as Partial<T>);
}