Google Places Sync & Caching
Relevant source files
The following files were used as context for generating this wiki page:
The Google Places Sync & Caching system is a core infrastructure component designed to reduce duplicate external API calls, bound operational costs, and manage Field Mask versioning for Google Places API (New). It implements a multi-layer strategy including database-level persistence in Supabase, in-memory negative caching for failed requests, and automated TTL (Time-To-Live) management.
The system ensures compliance with Google Maps Platform ToS by enforcing a maximum 30-day caching limit for Place responses while providing a seamless "read-through" interface for the application's AI agents and frontend components.
Sources: supabase/migrations/20260515043737_places_cache_schema.sql:1-12, src/lib/place-details-cache.ts:1-10
Architecture and Data Flow
The system operates as a read-through cache where the fetchPlaceDetailsWithCache function acts as the primary orchestrator. It first checks for a "negative cache" entry (indicating a recent failure for that ID), then attempts to read from the Supabase place_details_cache table. If both layers miss, it fetches fresh data from the Google Places API, writes it to the cache with an expiry timestamp, and returns the normalized result.
Cache Lookup Logic
The following diagram illustrates the sequence of operations when the system requests details for a specific Google Place ID:
Database Schema and TTL Strategy
The system utilizes two primary tables in the public schema of the Supabase database. These tables are protected by Row Level Security (RLS) and are accessible only to the service_role.
place_details_cache
This table stores enriched details for specific locations. It uses a composite uniqueness logic based on the place_id and the field_mask_version to ensure that if the application starts requesting more fields (e.g., adding editorialSummary), the cache automatically "misses" and refreshes.
| Field | Type | Description |
|---|---|---|
place_id | text | Primary Key. The Google Place ID (e.g., ChIJ...). |
field_mask_version | text | Identifier for the version of requested fields. |
payload | jsonb | The full raw response from the Google Places API. |
expires_at | timestamptz | Expiration date (Default: 7-14 days). |
display_name | text | Denormalized name for debugging and quick UI usage. |
places_search_cache
This table caches the results of search queries (Text or Nearby search). It uses a SHA-256 hash of the query text and location parameters as a unique key.
| Field | Type | Description |
|---|---|---|
query_hash | text | SHA-256(query_text + location_key). |
payload | jsonb | Raw Places API response array. |
expires_at | timestamptz | Short-term TTL (Default: 48 hours). |
Sources: supabase/migrations/20260515043737_places_cache_schema.sql:18-62, src/lib/place-details-cache.ts:88-96
Field Mask Management
A critical feature of the implementation is the enforcement of explicit X-Goog-FieldMask headers. This is required by the Google Places API (New) to control billing costs and specify which fields are returned.
Mask Versioning
The PLACE_DETAILS_FIELD_MASK_VERSION constant (e.g., details-v3-links-2026-05-26) is used as a cache-busting mechanism. When this string is updated in the code, all existing cache entries are ignored because the SQL queries include an equality check on this column.
// Field mask versioning for cache invalidation
export const PLACE_DETAILS_FIELD_MASK_VERSION = "details-v3-links-2026-05-26";
export const PLACE_DETAILS_MVP_MASK = [
"id", "displayName", "formattedAddress", "location",
"googleMapsLinks", "rating", "userRatingCount",
"priceLevel", "currentOpeningHours", "photos", "types"
] as const;
Sources: src/mastra/lib/google-places-client.ts:33-47, src/lib/place-details-cache.ts:70-76
Error Handling and Negative Caching
To prevent the system from repeatedly calling the API for invalid Place IDs or during API outages (which would incur unnecessary latency and potential costs), the system implements "Negative Caching."
- Failure Detection: If
getPlaceDetailsthrows aPlacesRequestError, the system captures the failure. - In-Memory Recording: The
recordPlacesNegativeCache(placeId)function marks the ID as temporarily unavailable. - Bypass: Subsequent calls to
fetchPlaceDetailsWithCachefor that ID will throw immediately without hitting the database or the external API until the negative cache TTL expires.
Sources: src/lib/place-details-cache.ts:100-104, src/lib/place-details-cache.ts:119-122
API Implementation
The system is exposed via an internal API route used by the frontend and AI tools to retrieve and normalize place data.
GET /api/places/detail
This route takes a placeId and returns a normalized PlaceDetailsDto.
- Request Parameters:
placeId(string). - Headers:
X-Places-Cache: Returnshitormissto indicate cache status.Cache-Control: Implementsstale-while-revalidatefor browser-level caching.
- Error States:
400: Invalid Place ID.404: Place not found in Google.502: Google Places API request failed.503: Missing API Key configuration.
Sources: src/app/api/places/detail/route.ts:1-40, src/lib/place-details.ts:1-89
Summary
The Google Places Sync & Caching module provides a robust, cost-effective bridge between the application and the Google Places API (New). By combining a Supabase-backed persistent cache with Field Mask versioning and negative caching, the system ensures high availability of place data while strictly adhering to Google's ToS and minimizing API expenditure. It serves as the data foundation for grounded AI responses and venue detail panels across the Medellin discovery platform.