/** * rVnb Automerge document schemas. * * Community RV & camper rentals — trust-based vehicle sharing. * Granularity: one Automerge document per space (vehicles + rentals + endorsements). * DocId format: {space}:vnb:vehicles */ import type { DocSchema } from '../../shared/local-first/document'; // ── Economy model ── export type EconomyModel = 'gift' | 'suggested' | 'fixed' | 'sliding_scale' | 'exchange'; // ── Vehicle types ── export type VehicleType = | 'motorhome' | 'camper_van' | 'travel_trailer' | 'truck_camper' | 'skoolie' | 'other'; // ── Fuel types ── export type FuelType = 'gas' | 'diesel' | 'electric' | 'hybrid' | 'propane' | 'other'; // ── Mileage policy ── export type MileagePolicy = 'unlimited' | 'per_mile' | 'included_miles'; // ── Rental request status ── export type RentalStatus = | 'pending' | 'accepted' | 'declined' | 'cancelled' | 'completed' | 'endorsed'; // ── Endorsement visibility ── export type EndorsementVisibility = 'public' | 'private' | 'community'; // ── Core types ── export interface TripWindow { id: string; vehicleId: string; startDate: number; // epoch ms (start of day) endDate: number; // epoch ms (end of day) status: 'available' | 'blocked' | 'tentative'; pickupLocationName: string | null; pickupLat: number | null; pickupLng: number | null; dropoffLocationName: string | null; dropoffLat: number | null; dropoffLng: number | null; notes: string | null; createdAt: number; } export interface Vehicle { id: string; ownerDid: string; // DID of the vehicle owner ownerName: string; title: string; description: string; type: VehicleType; economy: EconomyModel; // Vehicle specs year: number | null; make: string | null; model: string | null; lengthFeet: number | null; sleeps: number; fuelType: FuelType | null; // Amenities (boolean flags) hasGenerator: boolean; hasSolar: boolean; hasAC: boolean; hasHeating: boolean; hasShower: boolean; hasToilet: boolean; hasKitchen: boolean; petFriendly: boolean; towRequired: boolean; // Mileage policy mileagePolicy: MileagePolicy; includedMiles: number | null; perMileRate: number | null; // Pricing (relevant for non-gift economies) suggestedAmount: number | null; // suggested/fixed price per night currency: string | null; slidingMin: number | null; slidingMax: number | null; exchangeDescription: string | null; // Pickup / Dropoff pickupLocationName: string; pickupLocationLat: number | null; pickupLocationLng: number | null; dropoffSameAsPickup: boolean; dropoffLocationName: string | null; dropoffLocationLat: number | null; dropoffLocationLng: number | null; // Photos photos: string[]; coverPhoto: string | null; // Trust & auto-accept trustThreshold: number | null; instantAccept: boolean; // Metadata isActive: boolean; createdAt: number; updatedAt: number; } export interface RentalMessage { id: string; senderDid: string; senderName: string; body: string; sentAt: number; } export interface RentalRequest { id: string; vehicleId: string; renterDid: string; renterName: string; ownerDid: string; // Dates pickupDate: number; // epoch ms dropoffDate: number; // epoch ms estimatedMiles: number | null; // Requested locations requestedPickupLocation: string | null; requestedPickupLat: number | null; requestedPickupLng: number | null; requestedDropoffLocation: string | null; requestedDropoffLat: number | null; requestedDropoffLng: number | null; // Status flow: pending -> accepted/declined -> completed -> endorsed status: RentalStatus; // Messages embedded in the CRDT messages: RentalMessage[]; // Contribution (for non-gift economies) offeredAmount: number | null; offeredCurrency: string | null; offeredExchange: string | null; // Timestamps requestedAt: number; respondedAt: number | null; completedAt: number | null; cancelledAt: number | null; } export interface Endorsement { id: string; rentalId: string; vehicleId: string; // Who wrote it and about whom authorDid: string; authorName: string; subjectDid: string; subjectName: string; direction: 'renter_to_owner' | 'owner_to_renter'; // Content body: string; rating: number | null; // 1-5, optional tags: string[]; visibility: EndorsementVisibility; // Trust integration trustWeight: number; // 0-1 createdAt: number; } export interface SpaceConfig { defaultEconomy: EconomyModel; defaultTrustThreshold: number; endorsementTagCatalog: string[]; requireEndorsement: boolean; maxRentalDays: number; } // ── Top-level document ── export interface VnbDoc { meta: { module: string; collection: string; version: number; spaceSlug: string; createdAt: number; }; config: SpaceConfig; vehicles: Record; availability: Record; rentals: Record; endorsements: Record; } // ── Schema registration ── export const DEFAULT_ENDORSEMENT_TAGS = [ 'reliable', 'clean', 'suspiciously_clean', 'great_mileage', 'cozy', 'didnt_break_down', 'smells_like_adventure', 'felt_like_home', 'better_than_a_hotel', 'surprisingly_spacious', 'good_communication', 'smooth_handoff', ]; const DEFAULT_CONFIG: SpaceConfig = { defaultEconomy: 'suggested', defaultTrustThreshold: 30, endorsementTagCatalog: DEFAULT_ENDORSEMENT_TAGS, requireEndorsement: false, maxRentalDays: 30, }; export const vnbSchema: DocSchema = { module: 'vnb', collection: 'vehicles', version: 1, init: (): VnbDoc => ({ meta: { module: 'vnb', collection: 'vehicles', version: 1, spaceSlug: '', createdAt: Date.now(), }, config: { ...DEFAULT_CONFIG }, vehicles: {}, availability: {}, rentals: {}, endorsements: {}, }), }; // ── Helpers ── export function vnbDocId(space: string) { return `${space}:vnb:vehicles` as const; } /** Economy model display labels */ export const ECONOMY_LABELS: Record = { gift: 'Gift Economy', suggested: 'Suggested Contribution', fixed: 'Fixed Price', sliding_scale: 'Sliding Scale', exchange: 'Skill/Service Exchange', }; /** Vehicle type display labels */ export const VEHICLE_TYPE_LABELS: Record = { motorhome: 'Motorhome', camper_van: 'Camper Van', travel_trailer: 'Travel Trailer', truck_camper: 'Truck Camper', skoolie: 'Skoolie', other: 'Other', }; /** Vehicle type icons */ export const VEHICLE_TYPE_ICONS: Record = { motorhome: '\u{1F690}', // minibus camper_van: '\u{1F68C}', // bus travel_trailer: '\u{1F3D5}', // camping truck_camper: '\u{1F6FB}', // pickup truck skoolie: '\u{1F68E}', // trolleybus other: '\u{1F3E0}', // house }; /** Mileage policy display labels */ export const MILEAGE_LABELS: Record = { unlimited: 'Unlimited Miles', per_mile: 'Per Mile', included_miles: 'Included Miles', };