UNPKG

@simpleapps-com/augur-api

Version:

TypeScript client library for Augur microservices API endpoints

887 lines (718 loc) โ€ข 23.8 kB
# Augur API Tips and Tricks This document contains advanced usage patterns, performance optimizations, and helpful techniques for working with the Augur API client library. ## ๐Ÿ” Finding Items by ItemId ### Problem: You have an itemId but need the invMastUid When you only have a Prophet 21 itemId (like `WIRE-12-AWG-250FT`) but need the internal `invMastUid` for database operations: ```typescript // โœ… Use invMastUid = 0 with itemId as query pattern const itemLookup = await api.items.invMast.doc.get(0, { q: 'WIRE-12-AWG-250FT' // Your actual itemId }); // Extract the invMastUid for future direct lookups const invMastUid = itemLookup.data.invMastUid; // Now use the invMastUid for efficient direct access const itemDetails = await api.items.invMast.get(invMastUid); const locations = await api.items.invMast.locations.list(invMastUid); ``` ### Why this works - The Items service recognizes `invMastUid = 0` as a special lookup case - It searches the `item_id` field instead of using direct UID lookup - Returns the complete document including the actual `invMastUid` - This is the most efficient way to bridge itemId โ†’ invMastUid ## ๐Ÿš€ Performance Optimization ### Smart Caching Strategies ```typescript // โœ… Cache stable reference data with longer TTL const categories = await api.items.categories.list({ edgeCache: 8 // 8 hours - categories rarely change }); const brands = await api.items.brands.list({ edgeCache: 12 // 12 hours - brand data is very stable }); // โœ… Shorter cache for semi-dynamic data const inventory = await api.items.invMast.get(invMastUid, { edgeCache: 2 // 2 hours - inventory levels change regularly }); // โœ… Very short cache for user-specific data const userCart = await api.commerce.cartHeaders.list({ userId: 123, edgeCache: 0.5 // 30 minutes - cart changes frequently }); // โŒ Don't cache authentication or real-time operations const authCheck = await api.joomla.users.verifyPassword({ username: 'user', password: 'pass' // No edgeCache - authentication must be real-time }); ``` ### Batch Operations for Better Performance ```typescript // โœ… Get multiple items efficiently const itemPromises = invMastUids.map(uid => api.items.invMast.get(uid, { edgeCache: 2 }) ); const items = await Promise.all(itemPromises); // โœ… Use parallel requests for independent data const [categories, brands, attributes] = await Promise.all([ api.items.categories.list({ edgeCache: 8 }), api.items.brands.list({ edgeCache: 8 }), api.items.attributes.list({ edgeCache: 6 }) ]); ``` ## ๐Ÿ”„ Cross-Service Data Patterns ### Order โ†’ Item Details Workflow ```typescript // Get order details const order = await api.orders.oeHdr.get(orderNo); const orderLines = await api.orders.oeHdr.lines.list(orderNo); // Extract item IDs from order lines and get detailed item info const itemDetailsPromises = orderLines.data.map(async (line) => { // Use the itemId to invMastUid lookup pattern const itemLookup = await api.items.invMast.doc.get(0, { q: line.itemId }); return { orderLine: line, itemDetails: await api.items.invMast.get(itemLookup.data.invMastUid), locations: await api.items.invMast.locations.list(itemLookup.data.invMastUid) }; }); const enrichedOrderLines = await Promise.all(itemDetailsPromises); ``` ### Customer โ†’ Purchased Items โ†’ Recommendations ```typescript // Get customer's purchase history const customerId = 12345; const purchasedItems = await api.customers.customer.purchasedItems.list(customerId, { limit: 50, edgeCache: 1 // Recent purchases change frequently }); // Find similar items for recommendations using OpenSearch const recommendationPromises = purchasedItems.data.slice(0, 5).map(async (item) => { return api.openSearch.itemSearch.search({ q: item.itemId, searchType: 'similarity', size: 3, edgeCache: 4 // Similarity results are relatively stable }); }); const recommendations = await Promise.all(recommendationPromises); ``` ## ๐Ÿ›  Error Handling Patterns ### Graceful Degradation ```typescript async function getItemWithFallback(itemIdentifier: string | number) { try { // Try direct invMastUid lookup first if (typeof itemIdentifier === 'number') { return await api.items.invMast.get(itemIdentifier); } // Fallback to itemId lookup const itemLookup = await api.items.invMast.doc.get(0, { q: itemIdentifier }); return await api.items.invMast.get(itemLookup.data.invMastUid); } catch (error) { // Final fallback to search console.warn(`Direct lookup failed for ${itemIdentifier}, trying search...`); const searchResults = await api.openSearch.itemSearch.search({ q: itemIdentifier.toString(), searchType: 'query', size: 1 }); if (searchResults.data.length === 0) { throw new Error(`Item not found: ${itemIdentifier}`); } return searchResults.data[0]; } } ``` ### Request Timeout Handling ```typescript import { AbortController } from 'node-abort-controller'; async function getItemWithTimeout(invMastUid: number, timeoutMs = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const result = await api.items.invMast.get(invMastUid, {}, { signal: controller.signal }); clearTimeout(timeoutId); return result; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error(`Request timed out after ${timeoutMs}ms`); } throw error; } } ``` ## ๐Ÿ” Authentication Patterns ### Cross-Site Authentication ```typescript // Set up cross-site authentication for multi-tenant scenarios const api = createAugurApiClient({ baseUrl: 'https://api.example.com', authToken: 'your-jwt-token', siteId: 'site-specific-id' // For cross-site operations }); // Use with services that support cross-site operations const siteSpecificUsers = await api.joomla.users.list({ limit: 50 // siteId is automatically included in headers }); ``` ### Token Refresh Pattern ```typescript class ApiManager { private client: AugurApiClient; private refreshTokenIfNeeded: () => Promise<string>; constructor(refreshCallback: () => Promise<string>) { this.refreshTokenIfNeeded = refreshCallback; this.client = createAugurApiClient({ baseUrl: 'https://api.example.com' }); } async makeAuthenticatedRequest<T>( request: (client: AugurApiClient) => Promise<T> ): Promise<T> { try { return await request(this.client); } catch (error) { if (error.response?.status === 401) { // Token expired, refresh and retry const newToken = await this.refreshTokenIfNeeded(); this.client.updateAuthToken(newToken); return await request(this.client); } throw error; } } } ``` ## ๐Ÿ“Š Data Transformation Helpers ### Prophet 21 to Application Models ```typescript // Transform P21 data structures to application-friendly formats function transformInventoryItem(p21Item: any) { return { id: p21Item.invMastUid, itemId: p21Item.itemId, description: p21Item.itemDesc, price: p21Item.listPrice, availability: { inStock: p21Item.qtyOnHand > 0, quantity: p21Item.qtyOnHand, locations: p21Item.locations?.map(loc => ({ warehouse: loc.locationId, bin: loc.bin, quantity: loc.qtyOnHand })) || [] }, category: { id: p21Item.itemCategoryUid, name: p21Item.categoryName } }; } // Use with API responses const rawItem = await api.items.invMast.get(invMastUid); const transformedItem = transformInventoryItem(rawItem.data); ``` ## ๐Ÿงช Testing Patterns ### Mock API Responses ```typescript // Create mock client for testing const mockApi = { items: { invMast: { get: jest.fn().mockResolvedValue({ status: 200, data: { invMastUid: 123, itemId: 'TEST-ITEM' } }), doc: { get: jest.fn().mockResolvedValue({ status: 200, data: { invMastUid: 123, itemId: 'TEST-ITEM' } }) } } } }; // Test the itemId lookup pattern test('should find item by itemId', async () => { const result = await getItemByItemId('TEST-ITEM'); expect(mockApi.items.invMast.doc.get).toHaveBeenCalledWith(0, { q: 'TEST-ITEM' }); }); ``` ## ๐ŸŽฏ Common Use Cases ### E-commerce Product Catalog ```typescript // Build complete product catalog with all necessary data async function buildProductCatalog(categoryIds: number[]) { const [categories, brands, attributes] = await Promise.all([ api.items.categories.list({ edgeCache: 8 }), api.items.brands.list({ edgeCache: 8 }), api.items.attributes.list({ edgeCache: 6 }) ]); const products = []; for (const categoryId of categoryIds) { const categoryItems = await api.openSearch.itemSearch.search({ itemCategoryUidList: categoryId.toString(), searchType: 'query', size: 100, edgeCache: 4 }); const enrichedItems = await Promise.all( categoryItems.data.map(async (item) => ({ ...item, locations: await api.items.invMast.locations.list(item.invMastUid, { edgeCache: 2 }), alternates: await api.items.invMast.alternateCode.list(item.invMastUid, { edgeCache: 4 }) })) ); products.push(...enrichedItems); } return { products, categories: categories.data, brands: brands.data }; } ``` ### Inventory Management Dashboard ```typescript // Real-time inventory status across multiple locations async function getInventoryDashboard(itemIds: string[]) { const inventoryData = await Promise.all( itemIds.map(async (itemId) => { // Use the special lookup pattern const itemLookup = await api.items.invMast.doc.get(0, { q: itemId }); const invMastUid = itemLookup.data.invMastUid; const [details, locations, movements] = await Promise.all([ api.items.invMast.get(invMastUid, { edgeCache: 1 }), api.items.invMast.locations.list(invMastUid, { edgeCache: 0.5 }), // Get recent activity (if available in your setup) api.legacy.inventory.activity?.(itemId) || Promise.resolve({ data: [] }) ]); return { itemId, invMastUid, details: details.data, locations: locations.data, totalQuantity: locations.data.reduce((sum, loc) => sum + loc.qtyOnHand, 0), recentActivity: movements.data }; }) ); return inventoryData; } ``` ## ๐Ÿš€ Advanced Request Patterns ### Request Cancellation ```typescript const controller = new AbortController(); // Start a request const usersPromise = api.joomla.users.list( { limit: 100 }, { signal: controller.signal } // Pass abort signal ); // Cancel the request if needed setTimeout(() => { controller.abort(); }, 5000); try { const users = await usersPromise; } catch (error) { if (error.name === 'AbortError') { console.log('Request was cancelled'); } } ``` ### Custom Headers and Timeouts ```typescript // Per-request configuration const users = await api.joomla.users.list( { limit: 20 }, { timeout: 10000, // 10 second timeout headers: { 'X-Custom-Header': 'value', 'X-Request-ID': 'unique-id' } } ); ``` ### Middleware Integration ```typescript // Add global request/response interceptors const api = new AugurAPI({ siteId: 'your-site-id', bearerToken: 'your-token', // Request interceptor onRequest: (config) => { console.log(`Making request to: ${config.url}`); config.headers['X-Request-Timestamp'] = Date.now().toString(); return config; }, // Response interceptor onResponse: (response) => { console.log(`Response status: ${response.status}`); return response; }, // Error interceptor onError: (error) => { console.error('API Error:', error.message); // Custom error handling logic throw error; } }); ``` ## ๐Ÿ— Framework Integration Patterns ### React Hook Pattern ```tsx import React, { useState, useEffect } from 'react'; import { AugurAPI, User, AugurAPIError, type AugurContext } from '@simpleapps-com/augur-api'; // Custom hook for Augur API - Context-aware approach function useAugurAPI(context?: AugurContext) { const [api] = useState(() => { if (context) { // Use context-aware creation (recommended) return AugurAPI.fromContext(context); } // Fallback to manual configuration return new AugurAPI({ siteId: process.env.REACT_APP_AUGUR_SITE_ID!, bearerToken: getStoredToken() }); }); return api; } // Component using the API const UserList: React.FC = () => { const api = useAugurAPI(); const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { async function fetchUsers() { try { setLoading(true); setError(null); const response = await api.joomla.users.list({ limit: 50, edgeCache: 2 // Cache for 2 hours }); setUsers(response.data); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } } fetchUsers(); }, [api]); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.username}</li> ))} </ul> ); }; ``` ### Next.js API Route Integration ```typescript // pages/api/users.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { AugurAPI } from '@simpleapps-com/augur-api'; const api = new AugurAPI({ siteId: process.env.AUGUR_SITE_ID!, bearerToken: process.env.AUGUR_TOKEN! }); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { try { const { limit = 10, offset = 0 } = req.query; const users = await api.joomla.users.list({ limit: parseInt(limit as string), offset: parseInt(offset as string), edgeCache: 4 // 4 hours for server-side caching }); res.status(200).json(users); } catch (error) { console.error('API Error:', error); res.status(500).json({ error: 'Failed to fetch users' }); } } ``` ### React Native Pattern ```typescript // useAugurAPI.ts - React Native hook import { useState, useEffect } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { AugurAPI } from '@simpleapps-com/augur-api'; export function useAugurAPI() { const [api, setApi] = useState<AugurAPI | null>(null); const [isReady, setIsReady] = useState(false); useEffect(() => { async function initializeAPI() { try { const [siteId, token] = await Promise.all([ AsyncStorage.getItem('augur_site_id'), AsyncStorage.getItem('augur_token') ]); if (siteId && token) { const apiInstance = new AugurAPI({ siteId, bearerToken: token }); setApi(apiInstance); } } catch (error) { console.error('Failed to initialize API:', error); } finally { setIsReady(true); } } initializeAPI(); }, []); return { api, isReady }; } ``` ## ๐Ÿ›ก๏ธ Advanced Error Handling ### Comprehensive Error Handling Pattern ```typescript import { AugurAPIError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError } from '@simpleapps-com/augur-api'; async function handleAPICall() { try { const result = await api.joomla.users.list({ limit: 10 }); return result; } catch (error) { // Handle specific error types if (error instanceof AuthenticationError) { console.error('Authentication failed:', error.message); switch (error.statusCode) { case 401: await refreshToken(); break; default: redirectToLogin(); } } else if (error instanceof ValidationError) { console.error('Request validation failed:', error.validationErrors); error.validationErrors?.forEach(validationError => { console.error(`Field ${validationError.path}: ${validationError.message}`); }); } else if (error instanceof NotFoundError) { console.error('Resource not found:', error.message); showNotFoundMessage(error.endpoint); } else if (error instanceof RateLimitError) { console.error('Rate limit exceeded:', error.message); // Implement exponential backoff await new Promise(resolve => setTimeout(resolve, 2000)); return handleAPICall(); // Retry } else if (error instanceof AugurAPIError) { console.error('API error:', { code: error.code, message: error.message, service: error.service, endpoint: error.endpoint, statusCode: error.statusCode, requestId: error.requestId }); switch (error.statusCode) { case 500: showErrorMessage('Server error. Please try again later.'); break; case 503: showErrorMessage('Service temporarily unavailable.'); break; default: showErrorMessage(`API error: ${error.message}`); } } throw error; // Re-throw if needed } } ``` ### Retry Logic with Exponential Backoff ```typescript async function apiCallWithRetry<T>( operation: () => Promise<T>, maxRetries = 3, baseDelay = 1000 ): Promise<T> { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { // Don't retry on authentication or validation errors if (error instanceof AuthenticationError || error instanceof ValidationError) { throw error; } // Don't retry on final attempt if (attempt === maxRetries) { throw error; } // Calculate delay with exponential backoff const delay = baseDelay * Math.pow(2, attempt - 1); console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error('All retry attempts exhausted'); } // Usage const users = await apiCallWithRetry(() => api.joomla.users.list({ limit: 10 }) ); ``` ### Global Error Handler ```typescript class ErrorHandler { static handle(error: unknown, context?: string): void { const contextPrefix = context ? `[${context}] ` : ''; if (error instanceof AuthenticationError) { console.error(`${contextPrefix}Authentication failed:`, error.message); this.handleAuthError(error); } else if (error instanceof ValidationError) { console.error(`${contextPrefix}Validation failed:`, error.validationErrors); this.handleValidationError(error); } else if (error instanceof AugurAPIError) { console.error(`${contextPrefix}API error:`, { service: error.service, endpoint: error.endpoint, status: error.statusCode, message: error.message }); this.handleAPIError(error); } else { console.error(`${contextPrefix}Unexpected error:`, error); } } private static handleAuthError(error: AuthenticationError) { // Implement token refresh logic if (typeof window !== 'undefined') { window.location.href = '/login'; } } private static handleValidationError(error: ValidationError) { // Show user-friendly validation messages if (error.validationErrors) { error.validationErrors.forEach(err => { showFieldError(err.path, err.message); }); } } private static handleAPIError(error: AugurAPIError) { // Log to monitoring service if (typeof window !== 'undefined' && window.analytics) { window.analytics.track('API Error', { service: error.service, endpoint: error.endpoint, statusCode: error.statusCode }); } } } ``` ## ๐ŸŽ๏ธ Advanced Caching Strategies ### Edge Cache Duration Guide | Value | Duration | Best For | Example Use Cases | |-------|----------|----------|-------------------| | `1` | 1 hour | Frequently changing data | Cart contents, user sessions, real-time pricing | | `2` | 2 hours | Moderate volatility | User lists, inventory levels, order status | | `3` | 3 hours | Standard business data | Standard pricing, product information, customer data | | `4` | 4 hours | Semi-static data | Recommendations, warehouse data, shipping rates | | `5` | 5 hours | Reference data | Tags, categories, attribute lists | | `8` | 8 hours | Static content | Brand information, distributor details, system configs | ### Smart Cache Implementation ```typescript // Cache strategy based on data volatility class CacheStrategy { static getOptimalTTL(dataType: string, context?: any): number { switch (dataType) { case 'user_session': case 'cart_contents': case 'real_time_pricing': return 1; // 1 hour case 'inventory_levels': case 'order_status': case 'user_profiles': return 2; // 2 hours case 'product_catalog': case 'standard_pricing': case 'customer_data': return 3; // 3 hours case 'recommendations': case 'warehouse_locations': case 'shipping_rates': return 4; // 4 hours case 'categories': case 'attributes': case 'tags': return 5; // 5 hours case 'brands': case 'system_config': case 'distributor_info': return 8; // 8 hours default: return 2; // Safe default } } } // Usage in API calls const categories = await api.items.categories.list({ edgeCache: CacheStrategy.getOptimalTTL('categories') }); const userCart = await api.commerce.cartHeaders.list({ userId: 123, edgeCache: CacheStrategy.getOptimalTTL('cart_contents') }); ``` ### Cache Invalidation Patterns ```typescript // Cache warming for critical data async function warmCache() { const criticalData = [ () => api.items.categories.list({ edgeCache: 8 }), () => api.items.brands.list({ edgeCache: 8 }), () => api.items.attributes.list({ edgeCache: 5 }), ]; // Warm cache in parallel await Promise.all(criticalData.map(fn => fn())); } // Cache busting for updated data async function updateProductAndInvalidateCache(productId: number, updates: any) { // Update the product await api.items.invMast.update(productId, updates); // Force refresh cached data (edgeCache: 0 bypasses cache) const freshData = await api.items.invMast.get(productId, { edgeCache: 0 }); return freshData; } ``` --- ## ๐Ÿ’ก Pro Tips 1. **Always cache reference data** - Categories, brands, and attributes change infrequently 2. **Use the itemId โ†’ invMastUid lookup** - It's the bridge between user-facing IDs and internal UIDs 3. **Batch related requests** - Use `Promise.all()` for independent parallel requests 4. **Handle 404s gracefully** - Not all items exist in all systems 5. **Monitor cache hit rates** - Adjust TTL values based on actual usage patterns 6. **Use TypeScript** - The client provides full type safety for better development experience --- *This document is part of the [Augur API Client Library](./README.md) package and is included in npm distributions for offline reference.*