You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The ogc-client-CSAPI implementation will have HTTP ETag caching (work item #39) and ParseResult caching (work item #40), but lacks sophisticated cache invalidation strategies beyond manual clearing and automatic invalidation on mutations.
Current Planned Invalidation (from work items #39 and #40):
Stale Data: Users see outdated data after TTL expires but before next fetch
Inefficient Refresh: Full cache expiration instead of targeted invalidation
Poor UX: Loading states when stale data could be shown immediately
Server Load: No background refresh means thundering herd on expiration
Wasted Bandwidth: No server guidance on cache lifetime
Inconsistent State: Related resources out of sync
Example Scenario (Current Behavior):
// At T=0: Cache System (TTL: 5 minutes)constsystem=awaitnav.getSystem('sensor-123');// At T=4:59: Cache still validconstsystem2=awaitnav.getSystem('sensor-123');// Instant// At T=5:01: Cache expiredconstsystem3=awaitnav.getSystem('sensor-123');// Full refetch, user waits// At T=5:02: User deletes a Deployment of this System on another tab// Cache still shows old Deployment in System's deployment list
With Advanced Invalidation (Desired Behavior):
// At T=0: Cache System (TTL: 5 minutes)constsystem=awaitnav.getSystem('sensor-123');// At T=4:30: Background refresh starts (before expiration)// User gets instant response with cached data// At T=4:35: Background refresh completes// Cache updated transparently// At T=5:01: Cache still fresh (was refreshed at T=4:35)constsystem2=awaitnav.getSystem('sensor-123');// Instant// At T=5:02: User deletes Deployment on another tab// Event-based invalidation: System cache invalidated// Related Deployment collection cache invalidatedconstsystem3=awaitnav.getSystem('sensor-123');// Fetches fresh data
Context
This issue was identified during the comprehensive validation conducted January 27-28, 2026.
Related Validation Issues:#13 (CSAPI Navigator Implementation), #16 (TypedCSAPINavigator)
❌ Cache-Control: must-revalidate (server requires fresh data after expiration)
❌ Expires header (legacy cache expiration)
Implication: Server cannot control client cache behavior, leading to stale or overly fresh data.
4. Invalidation Patterns from OGC API Best Practices
OGC API - Features Caching Recommendations:
Servers SHOULD provide Cache-Control headers with appropriate max-age
Clients SHOULD respect server cache directives
Stable resources (Systems, Deployments) can have longer cache times (hours)
Dynamic resources (Observations) should have short cache times (seconds/minutes)
Common Cache-Control Patterns:
# Stable System (cache for 1 hour)Cache-Control: max-age=3600# Moderately stable Datastream (cache for 5 minutes)Cache-Control: max-age=300# Real-time Observation (don't cache)Cache-Control: no-cache, no-store, must-revalidate# Stale-while-revalidate (return stale up to 60s while refreshing)Cache-Control: max-age=300, stale-while-revalidate=60
Current Implementation: None of these directives are honored.
5. Performance Impact of Invalidation Strategies
Scenario: Dashboard with 10 Systems, refreshing every 5 seconds
Without Advanced Invalidation (Current):
T=0:00 - Fetch 10 Systems (cache for 2 minutes)
T=0:05 - Return cached Systems (instant)
T=0:10 - Return cached Systems (instant)
...
T=2:00 - Cache expired, fetch 10 Systems (user waits)
T=2:05 - Return cached Systems (instant)
...
Issues:
- User waits at T=2:00, T=4:00, T=6:00 (every 2 minutes)
- Thundering herd: All 10 Systems refetch simultaneously
- No stale data shown during refresh
With Stale-While-Revalidate:
T=0:00 - Fetch 10 Systems (cache for 2 minutes, stale-revalidate for 30s)
T=0:05 - Return cached Systems (instant)
T=0:10 - Return cached Systems (instant)
...
T=2:00 - Cache expired but within stale window
→ Return stale cached Systems (instant)
→ Background fetch starts
T=2:02 - Background fetch completes, cache updated
T=2:05 - Return fresh cached Systems (instant)
Benefits:
- User NEVER waits (stale data returned immediately)
- Background refresh transparent
- Better perceived performance
With Background Refresh (Before Expiration):
T=0:00 - Fetch 10 Systems (cache for 2 minutes)
T=0:05 - Return cached Systems (instant)
...
T=1:50 - Background refresh starts (10 seconds before expiration)
T=1:52 - Background refresh completes, cache updated
T=2:00 - Return fresh cached Systems (instant, never expired)
Benefits:
- Cache never expires (always refreshed before expiration)
- User never sees loading state
- Smooth, uninterrupted experience
With Event-Based Invalidation:
T=0:00 - Fetch 10 Systems, subscribe to WebSocket updates
T=0:05 - Return cached Systems (instant)
T=1:00 - Server sends event: System 'sensor-123' updated
→ Invalidate 'sensor-123' cache
→ Background fetch 'sensor-123'
T=1:02 - Cache updated with fresh 'sensor-123'
T=1:05 - Return cached Systems (9 stale, 1 fresh)
Benefits:
- Only changed resources refetched
- Near-instant updates (server-pushed)
- Minimal bandwidth usage
Proposed Solution
1. Time-Based Invalidation Strategies
A. Stale-While-Revalidate Pattern
Implement RFC 5861 stale-while-revalidate:
interfaceCacheEntry{url: string;data: any;etag?: string;timestamp: number;ttl: number;// Normal cache lifetimestaleWhileRevalidate?: number;// Additional time to serve stale}classCacheManager{asyncget(url: string): Promise<CacheEntry|undefined>{constentry=this.cache.get(url);if(!entry)returnundefined;constage=Date.now()-entry.timestamp;// Fresh: Within TTLif(age<entry.ttl){returnentry;}// Stale but revalidate: Within stale-while-revalidate windowif(entry.staleWhileRevalidate&&age<entry.ttl+entry.staleWhileRevalidate){// Return stale data immediatelyconststaleEntry={ ...entry};// Start background revalidationthis.revalidateInBackground(url).catch(err=>{console.warn('Background revalidation failed:',err);});returnstaleEntry;}// Expired: Remove from cachethis.cache.delete(url);returnundefined;}privateasyncrevalidateInBackground(url: string): Promise<void>{// Fetch fresh data without blockingconstfresh=awaitthis.fetch(url);this.cache.set(url,fresh);}}
B. Background Refresh (Before Expiration)
Proactively refresh cache before expiration:
interfaceCacheEntry{url: string;data: any;timestamp: number;ttl: number;refreshThreshold: number;// Refresh when age > ttl - refreshThreshold}classCacheManager{asyncget(url: string): Promise<CacheEntry|undefined>{constentry=this.cache.get(url);if(!entry)returnundefined;constage=Date.now()-entry.timestamp;// Check if refresh neededif(age>entry.ttl-entry.refreshThreshold){// Start background refresh (don't wait)this.refreshInBackground(url).catch(err=>{console.warn('Background refresh failed:',err);});}// Return current data (even if refresh started)if(age<entry.ttl){returnentry;}returnundefined;}privateasyncrefreshInBackground(url: string): Promise<void>{constfresh=awaitthis.fetch(url);this.cache.set(url,fresh);}}
C. Adaptive TTL
Adjust TTL based on resource update frequency:
interfaceCacheEntry{url: string;data: any;timestamp: number;ttl: number;updateHistory: number[];// Timestamps of last N updates}classCacheManager{privatecalculateAdaptiveTTL(entry: CacheEntry): number{if(entry.updateHistory.length<2){returnentry.ttl;// Not enough data}// Calculate average time between updatesconstintervals: number[]=[];for(leti=1;i<entry.updateHistory.length;i++){intervals.push(entry.updateHistory[i]-entry.updateHistory[i-1]);}constavgInterval=intervals.reduce((a,b)=>a+b)/intervals.length;// Set TTL to 50% of average update interval// (refresh before next update expected)constadaptiveTTL=avgInterval*0.5;// Clamp to reasonable range (30s - 1 hour)returnMath.max(30000,Math.min(adaptiveTTL,3600000));}}
2. Event-Based Invalidation
A. WebSocket Notifications
Server pushes cache invalidation events:
classCacheInvalidationSubscriber{privatews: WebSocket|null=null;constructor(privatebaseUrl: string,privatecacheManager: CacheManager){}connect(): void{constwsUrl=this.baseUrl.replace('http','ws')+'/cache-events';this.ws=newWebSocket(wsUrl);this.ws.onmessage=(event)=>{constmessage=JSON.parse(event.data);this.handleInvalidationEvent(message);};}privatehandleInvalidationEvent(event: InvalidationEvent): void{switch(event.type){case'resource-updated':
// Invalidate specific resourcethis.cacheManager.invalidate(event.resourceUrl);break;case'resource-deleted':
// Invalidate resource and related collectionsthis.cacheManager.invalidate(event.resourceUrl);this.cacheManager.invalidateCollections(event.resourceType);break;case'collection-changed':
// Invalidate entire collectionthis.cacheManager.invalidateResourceType(event.resourceType);break;}}}interfaceInvalidationEvent{type: 'resource-updated'|'resource-deleted'|'collection-changed';resourceUrl?: string;resourceType?: CSAPIResourceType;timestamp: number;}
Track resource relationships and invalidate cascading:
classDependencyTracker{privatedependencies=newMap<string,Set<string>>();// Register dependency: parent depends on childaddDependency(parentUrl: string,childUrl: string): void{if(!this.dependencies.has(parentUrl)){this.dependencies.set(parentUrl,newSet());}this.dependencies.get(parentUrl)!.add(childUrl);}// Get all URLs that depend on this URLgetDependents(url: string): Set<string>{constdependents=newSet<string>();for(const[parent,children]ofthis.dependencies.entries()){if(children.has(url)){dependents.add(parent);}}returndependents;}// Invalidate URL and all dependentsinvalidateCascading(url: string,cacheManager: CacheManager): void{// Invalidate the URL itselfcacheManager.invalidate(url);// Invalidate all dependents recursivelyconstdependents=this.getDependents(url);for(constdependentofdependents){this.invalidateCascading(dependent,cacheManager);}}}// Usageconsttracker=newDependencyTracker();// When fetching System Datastreams, register dependencyconstsystemUrl=nav.getSystemUrl('sensor-123');constdatastreamsUrl=nav.getSystemDatastreamsUrl('sensor-123');tracker.addDependency(systemUrl,datastreamsUrl);// When a Datastream is updated, invalidate SystemconstdatastreamUrl=nav.getDatastreamUrl('datastream-456');tracker.invalidateCascading(datastreamUrl,cacheManager);// → Invalidates datastream-456// → Invalidates sensor-123 (depends on its datastreams)
Recommendation: Implement after work items #39 and #40 are complete, stable, and proven effective. Prioritize stale-while-revalidate and Cache-Control support first (highest impact), then event-based invalidation if server supports it, and finally dependency tracking as polish.
Problem
The ogc-client-CSAPI implementation will have HTTP ETag caching (work item #39) and ParseResult caching (work item #40), but lacks sophisticated cache invalidation strategies beyond manual clearing and automatic invalidation on mutations.
Current Planned Invalidation (from work items #39 and #40):
clearCache(),clearCacheForResourceType()Missing Strategies:
1. Time-Based Invalidation (TTL with Stale-While-Revalidate)
2. Event-Based Invalidation
3. Dependency-Based Invalidation
4. Server-Driven Invalidation (Cache-Control Headers)
Cache-Control: max-agefrom serverCache-Control: no-cachedirectiveCache-Control: must-revalidateReal-World Impact:
Example Scenario (Current Behavior):
With Advanced Invalidation (Desired Behavior):
Context
This issue was identified during the comprehensive validation conducted January 27-28, 2026.
Related Validation Issues: #13 (CSAPI Navigator Implementation), #16 (TypedCSAPINavigator)
Work Item ID: 41 from Remaining Work Items
Repository: https://github.com/OS4CSAPI/ogc-client-CSAPI
Validated Commit:
a71706b9592cad7a5ad06e6cf8ddc41fa5387732Detailed Findings
1. Current Cache Infrastructure (Work Items #39 and #40)
From Work Item #39 (HTTP ETag Cache):
From Work Item #40 (ParseResult Cache):
What Works Well:
What's Missing:
2. Navigator Provides Resource Relationship Context (Issue #13)
From Issue #13 Validation Report:
Key Finding: Navigator understands resource relationships, providing foundation for dependency-based invalidation.
Evidence:
Implication: When a System is deleted, its related Datastreams, Deployments, and Procedures should be invalidated from cache.
3. TypedCSAPINavigator Fetches Resources (Issue #16)
From Issue #16 Validation Report:
Current
_fetch()Method:Key Finding: Response headers available but not used for cache control.
Missing Response Header Handling:
Cache-Control: max-age=3600(server specifies cache lifetime)Cache-Control: no-cache(server requires revalidation)Cache-Control: no-store(server forbids caching)Cache-Control: must-revalidate(server requires fresh data after expiration)Expiresheader (legacy cache expiration)Implication: Server cannot control client cache behavior, leading to stale or overly fresh data.
4. Invalidation Patterns from OGC API Best Practices
OGC API - Features Caching Recommendations:
Cache-Controlheaders with appropriatemax-ageCommon Cache-Control Patterns:
Current Implementation: None of these directives are honored.
5. Performance Impact of Invalidation Strategies
Scenario: Dashboard with 10 Systems, refreshing every 5 seconds
Without Advanced Invalidation (Current):
With Stale-While-Revalidate:
With Background Refresh (Before Expiration):
With Event-Based Invalidation:
Proposed Solution
1. Time-Based Invalidation Strategies
A. Stale-While-Revalidate Pattern
Implement RFC 5861 stale-while-revalidate:
B. Background Refresh (Before Expiration)
Proactively refresh cache before expiration:
C. Adaptive TTL
Adjust TTL based on resource update frequency:
2. Event-Based Invalidation
A. WebSocket Notifications
Server pushes cache invalidation events:
B. Server-Sent Events (SSE)
Alternative to WebSocket (simpler, HTTP-based):
C. Custom Event Bus
Client-side event coordination (for multi-tab scenarios):
3. Dependency-Based Invalidation
Track resource relationships and invalidate cascading:
4. Server-Driven Invalidation (Cache-Control)
Respect server cache directives:
5. Configuration
Invalidation Strategy Options:
Acceptance Criteria
Time-Based Invalidation (15 criteria)
Stale-While-Revalidate:
Background Refresh:
Adaptive TTL:
Event-Based Invalidation (12 criteria)
WebSocket Support:
resource-updatedevent (invalidate specific resource)resource-deletedevent (invalidate resource + collections)collection-changedevent (invalidate resource type)Server-Sent Events (SSE):
BroadcastChannel:
Dependency-Based Invalidation (8 criteria)
Server-Driven Invalidation (10 criteria)
Cache-Controlheader from responsesmax-agedirective (override default TTL)stale-while-revalidatedirectiveno-cachedirective (always revalidate)no-storedirective (don't cache)must-revalidatedirective (no stale serving)Expiresheader (legacy support)Configuration (6 criteria)
InvalidationOptionsto cache configurationTesting (25 criteria)
Stale-While-Revalidate Tests (6 tests):
Background Refresh Tests (4 tests):
Adaptive TTL Tests (4 tests):
Event-Based Tests (6 tests):
Dependency Tests (3 tests):
Cache-Control Tests (2 tests):
Documentation (5 criteria)
Implementation Notes
Files to Create
New File:
src/ogc-api/csapi/cache-invalidation.ts(~400-500 lines)InvalidationStrategyinterfaceStaleWhileRevalidateStrategyclassBackgroundRefreshStrategyclassAdaptiveTTLStrategyclassDependencyTrackerclassInvalidationOptionsinterfaceNew File:
src/ogc-api/csapi/cache-events.ts(~300-400 lines)CacheEventSubscriberinterfaceWebSocketSubscriberclassSSESubscriberclassBroadcastChannelSubscriberclassInvalidationEventinterfaceNew File:
src/ogc-api/csapi/cache-invalidation.spec.ts(~500-600 lines)Files to Modify
Modify:
src/ogc-api/csapi/http-cache.ts(from work item #39)Modify:
src/ogc-api/csapi/parse-result-cache.ts(from work item #40)Modify:
src/ogc-api/csapi/typed-navigator.tsInvalidationOptionsin constructor_fetch()Modify:
README.mdImplementation Phases
Phase 1: Time-Based Strategies (8-10 hours)
Phase 2: Server-Driven Invalidation (4-6 hours)
Phase 3: Event-Based Invalidation (10-12 hours)
Phase 4: Dependency Tracking (6-8 hours)
Phase 5: Integration and Documentation (6-8 hours)
Total Estimated Effort: 34-44 hours
Dependencies
Requires:
Blocks:
Critical Path:
Caveats
Server Support Required:
For Event-Based Invalidation:
For Server-Driven Invalidation:
Cache-ControlheadersPerformance Considerations:
Stale-While-Revalidate:
Background Refresh:
Event Subscriptions:
Dependency Tracking:
Browser Compatibility:
Testing Requirements
Unit Tests:
Integration Tests:
Performance Tests:
Manual Testing:
Priority Justification
Priority: Low
Why Low Priority:
Why Still Important:
Impact if Not Addressed:
When to Prioritize Higher:
Effort Estimate: 34-44 hours
ROI Analysis:
Recommendation: Implement after work items #39 and #40 are complete, stable, and proven effective. Prioritize stale-while-revalidate and Cache-Control support first (highest impact), then event-based invalidation if server supports it, and finally dependency tracking as polish.