UNPKG

ws-dottie

Version:

Your friendly TypeScript companion for Washington State transportation APIs - WSDOT and WSF data with smart caching and React Query integration

611 lines (492 loc) 16.8 kB
# Fetching Data Guide This guide covers how to fetch data using WS-Dottie's basic fetch functionality, including different options for fetching and validating data. > **📚 Documentation Navigation**: [Documentation Index](../../INDEX.md) [Getting Started](../../getting-started.md) [API Guide](../api-guide.md) ## 🚀 Quick Start ### 1. Install Dependencies ```bash # Install WS-Dottie npm install ws-dottie # Or with yarn yarn add ws-dottie ``` ### 2. Configure API Key ```javascript import { configManager } from 'ws-dottie'; // Set API key from environment variable configManager.setApiKey(process.env.WSDOT_ACCESS_TOKEN); // Optional: Set custom base URL (for proxying) configManager.setBaseUrl(process.env.WSDOT_BASE_URL); // Note: setLogLevel is currently a stub implementation // Use logMode parameter in fetch functions instead (see below) ``` ### 3. Basic Usage ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; import { fetchAlerts } from 'ws-dottie/wsdot-highway-alerts/core'; import { fetchBorderCrossings } from 'ws-dottie/wsdot-border-crossings/core'; async function fetchTransportationData() { try { // Fetch vessel locations (without validation - faster) const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: false, // Default: faster, no validation overhead logMode: 'none' // Default: no logging }); console.log(`Found ${vessels.length} vessels`); // Fetch highway alerts (with validation - safer) const alerts = await fetchAlerts({ fetchMode: 'native', validate: true, // Enable validation for extra safety logMode: 'info' // Log basic request information }); console.log(`Found ${alerts.length} alerts`); // Fetch border crossings (without validation - faster) const crossings = await fetchBorderCrossings({ fetchMode: 'native', validate: false, // Skip validation for better performance logMode: 'debug' // Detailed logging for debugging }); console.log(`Found ${crossings.length} border crossings`); return { vessels, alerts, crossings }; } catch (error) { console.error('Error fetching transportation data:', error.message); throw error; } } // Run function fetchTransportationData(); ``` ## 📦 Import Patterns WS-Dottie provides multiple import patterns optimized for different use cases. For server-side code (no React), use the `/core` subpath: **Core-Only Imports** (Recommended for server-side): ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; ``` **API-Specific Imports** (Includes hooks): ```javascript import { useVesselLocations, useVesselLocationsByVesselId } from 'ws-dottie/wsf-vessels'; import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; ``` Hooks now accept the same `FetchFunctionParams<T>` object as their matching fetch functions. Provide endpoint parameters with `params`, override transport with `fetchMode`, control Zod validation with `validate`, and set logging verbosity with `logMode`. ```javascript // Hook without endpoint parameters const { data: vessels } = useVesselLocations({ fetchMode: 'native', validate: false, logMode: 'none', // Optional: 'none' | 'info' | 'debug' }); // Hook with endpoint parameters const { data: vessel } = useVesselLocationsByVesselId({ params: { VesselID: 18 }, fetchMode: 'native', validate: true, logMode: 'info', // Optional: 'none' | 'info' | 'debug' }); ``` **Shared Utilities** (Config, fetch helper, dates): ```javascript import { configManager, fetchDottie } from 'ws-dottie'; ``` **Importing TypeScript Types:** ```typescript // Import types along with functions import { fetchVesselLocations, type VesselLocation, // Output type type VesselLocationsInput // Input type } from 'ws-dottie/wsf-vessels/core'; // Use types in your code async function processVessels(): Promise<VesselLocation[]> { const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: false }); return vessels; } ``` See the [Import Patterns section in README.md](../../README.md#5-import-patterns) for detailed guidance. ## 🏗️ Fetch Modes WS-Dottie supports different fetch modes for different environments: ### Native Fetch (Server-side) ```javascript // Native fetch for server-side applications const vessels = await fetchVesselLocations({ fetchMode: 'native', // Default for Node.js validate: true }); ``` ### JSONP (Browser) ```javascript // JSONP for browser applications (CORS compatibility) const vessels = await fetchVesselLocations({ fetchMode: 'jsonp', // Default for browser validate: true }); ``` ## 🎯 Common Patterns ### Data Fetching with Parameters ```javascript import { fetchScheduleByTripDateAndRouteId, fetchFareLineItemsByTripDateAndTerminals } from 'ws-dottie/wsf-schedule/core'; async function fetchFerrySchedule(departingTerminalId, arrivingTerminalId, tripDate) { try { // Fetch schedules const schedules = await fetchScheduleByTripDateAndRouteId({ params: { TripDate: tripDate.toISOString().split('T')[0], // Format as YYYY-MM-DD RouteID: departingTerminalId }, fetchMode: 'native', validate: true }); // Fetch fares const fares = await fetchFareLineItemsByTripDateAndTerminals({ params: { TripDate: tripDate.toISOString().split('T')[0], // Format as YYYY-MM-DD DepartingTerminalID: departingTerminalId, ArrivingTerminalID: arrivingTerminalId }, fetchMode: 'native', validate: true }); // Combine data const combinedData = { schedules: schedules.Sailings || [], fares: fares || [], route: { departing: departingTerminalId, arriving: arrivingTerminalId, date: tripDate } }; return combinedData; } catch (error) { console.error('Error fetching ferry schedule:', error.message); throw error; } } // Usage async function main() { const scheduleData = await fetchFerrySchedule(3, 7, new Date()); console.log('Schedule Data:'); console.log(`Route: ${scheduleData.route.departing} to ${scheduleData.route.arriving}`); console.log(`Date: ${scheduleData.route.date.toLocaleDateString()}`); console.log(`Sailings: ${scheduleData.schedules.length}`); console.log(`Fare Options: ${scheduleData.fares.length}`); } main(); ``` ### Batch Data Fetching ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; import { fetchTerminalWaitTimes } from 'ws-dottie/wsf-terminals/core'; import { fetchAlerts } from 'ws-dottie/wsdot-highway-alerts/core'; import { fetchBorderCrossings } from 'ws-dottie/wsdot-border-crossings/core'; async function fetchBatchData() { try { // Fetch multiple data sources in parallel const [vessels, waitTimes, alerts, crossings] = await Promise.all([ fetchVesselLocations({ fetchMode: 'native', validate: false // Default: faster }), fetchTerminalWaitTimes({ fetchMode: 'native', validate: false // Default: faster }), fetchAlerts({ fetchMode: 'native', validate: true // Enable validation for critical data }), fetchBorderCrossings({ fetchMode: 'native', validate: false // Default: faster }) ]); return { vessels, waitTimes, alerts, crossings, lastUpdated: new Date() }; } catch (error) { console.error('Error fetching batch data:', error.message); throw error; } } // Usage with caching const dataCache = new Map(); async function getCachedData(cacheKey, fetchFunction) { // Check cache first if (dataCache.has(cacheKey)) { const cached = dataCache.get(cacheKey); // Return cached data if not stale (5 minutes) if (Date.now() - cached.timestamp < 5 * 60 * 1000) { return cached.data; } } // Fetch fresh data const data = await fetchFunction(); // Update cache dataCache.set(cacheKey, { data, timestamp: Date.now() }); return data; } // Usage async function main() { const data = await getCachedData('transportation-overview', fetchBatchData); console.log('Transportation Overview:'); console.log(`Vessels: ${data.vessels.length}`); console.log(`Terminal Wait Times: ${data.waitTimes.length}`); console.log(`Highway Alerts: ${data.alerts.length}`); console.log(`Border Crossings: ${data.crossings.length}`); console.log(`Last Updated: ${data.lastUpdated.toLocaleString()}`); } main(); ``` ## 🔧 Validation Options WS-Dottie provides optional Zod schema validation. Validation is **disabled by default** (`validate: false`) for optimal performance, but you can enable it when you need extra safety. ### Understanding Validation **With validation enabled** (`validate: true`): - Validates API responses against Zod schemas at runtime - Catches API response shape changes immediately - Transforms date strings and nullable fields safely - Adds ~50-100KB to bundle size (Zod schemas) - Slightly slower due to schema parsing overhead **Without validation** (`validate: false` - default): - Faster performance — no schema parsing - Smaller bundle size — Zod schemas can be tree-shaken out - Still type-safe — TypeScript types are always available - Automatic .NET date conversion still applies - Perfect for production when API is stable ### Schema Validation ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; // Enable automatic validation (recommended for development) const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: true // Validates response against Zod schema }); // Disable validation (recommended for production) const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: false // Default: faster, no validation overhead }); ``` ### When Schemas Are Required If you request validation (`validate: true`) but schemas aren't available (e.g., using a light build), WS-Dottie will throw a clear error: ``` Validation requires schemas. This endpoint was built without schemas. Use the full build or import schemas from the /schemas subpath. ``` **Solutions:** 1. Use the full build (includes schemas by default) 2. Import schemas from the `/schemas` subpath: ```javascript import { vesselLocationsSchema } from 'ws-dottie/wsf-vessels/schemas'; ``` 3. Disable validation (`validate: false`) if you don't need it ### Production vs Development Patterns **Development Pattern** (catch issues early): ```javascript const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: process.env.NODE_ENV === 'development' }); ``` **Production Pattern** (optimize for performance): ```javascript const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: false // Faster, smaller bundle }); ``` **Conditional Pattern** (best of both worlds): ```javascript const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: process.env.NODE_ENV !== 'production' }); ``` ### Custom Validation If you need custom validation logic beyond Zod schemas: ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; async function fetchWithCustomValidation() { try { const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: false // Disable automatic validation }); // Custom validation logic const validVessels = vessels.filter(vessel => vessel.VesselID && vessel.VesselName && typeof vessel.Latitude === 'number' && typeof vessel.Longitude === 'number' ); if (validVessels.length !== vessels.length) { console.warn(`Filtered out ${vessels.length - validVessels.length} invalid vessels`); } return validVessels; } catch (error) { console.error('Error fetching vessels:', error.message); throw error; } } ``` ## 🚨 Error Handling ### Structured Error Handling ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; import { fetchBorderCrossings } from 'ws-dottie/wsdot-border-crossings/core'; import { isApiError } from 'ws-dottie'; class TransportationService { constructor() { this.retryConfig = { maxRetries: 3, baseDelay: 1000, // 1 second maxDelay: 10000, // 10 seconds backoffFactor: 2 }; } async fetchWithRetry(fetchFunction, params = {}) { let lastError; for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) { try { const data = await fetchFunction({ ...params, fetchMode: 'native', validate: false // Default: faster }); return data; } catch (error) { lastError = error; // Don't retry on authentication errors if (isApiError(error) && error.status === 401) { throw error; } // Don't retry on client errors (4xx) if (isApiError(error) && error.status >= 400 && error.status < 500) { throw error; } // If this is last attempt, throw error if (attempt === this.retryConfig.maxRetries) { throw error; } // Calculate delay for next attempt const delay = Math.min( this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffFactor, attempt - 1), this.retryConfig.maxDelay ); console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`); // Wait before retrying await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } async fetchVesselLocations() { try { return await this.fetchWithRetry(fetchVesselLocations); } catch (error) { console.error('Failed to fetch vessel locations after retries:', error.message); throw error; } } } // Usage const transportService = new TransportationService(); async function main() { try { const vessels = await transportService.fetchVesselLocations(); console.log(`Found ${vessels.length} vessels`); } catch (error) { console.error('Application error:', error.message); // Handle application-level error if (isApiError(error)) { console.error('API Error Details:', { status: error.status, message: error.message, context: error.context }); } } } main(); ``` ## 📊 Data Transformation ### Date/Time Handling ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; async function fetchWithDateTransformation() { try { const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: true }); // Transform data for easier use const transformedVessels = vessels.map(vessel => ({ ...vessel, // WS-Dottie automatically converts .NET dates to JavaScript Date objects // But you can add additional transformations if needed lastUpdatedFormatted: vessel.LastUpdated ? new Date(vessel.LastUpdated).toLocaleString() : 'Unknown', speedKnots: vessel.Speed || 0, location: { lat: vessel.Latitude, lng: vessel.Longitude } })); return transformedVessels; } catch (error) { console.error('Error fetching vessels:', error.message); throw error; } } ``` ### Field Filtering ```javascript import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core'; async function fetchWithFieldFiltering(fields = []) { try { const vessels = await fetchVesselLocations({ fetchMode: 'native', validate: true }); // Filter to only requested fields if (fields.length > 0) { return vessels.map(vessel => { const filtered = {}; fields.forEach(field => { if (vessel[field] !== undefined) { filtered[field] = vessel[field]; } }); return filtered; }); } return vessels; } catch (error) { console.error('Error fetching vessels:', error.message); throw error; } } // Usage const minimalVessels = await fetchWithFieldFiltering([ 'VesselID', 'VesselName', 'Latitude', 'Longitude' ]); ``` ## 📚 Next Steps - **[TanStack Query Guide](./tanstack-query.md)** - TanStack Query integration and caching - **[CLI Usage Guide](./cli-usage.md)** - Command-line interface and debugging - **[Error Handling Reference](./error-handling.md)** - WS-Dottie error types and recovery