@adventurelabs/scout-core 1.0.77 → 1.0.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Example usage of the ScoutCache system
3
+ * This demonstrates how to use the cache-first loading pattern
4
+ */
5
+ import { CacheStats, TimingStats } from "./cache";
6
+ export declare function ExampleBasicUsage(): {
7
+ handleRefresh: () => Promise<void>;
8
+ clearCache: () => Promise<void>;
9
+ stats: () => TimingStats;
10
+ cacheStats: () => Promise<CacheStats>;
11
+ };
12
+ export declare function ExampleAdvancedCacheManagement(): Promise<void>;
13
+ export declare function ExampleBackgroundPreloading(): Promise<void>;
14
+ export declare function ExamplePerformanceMonitoring(): {
15
+ getTimingStats: () => TimingStats;
16
+ getCacheStats: () => Promise<CacheStats>;
17
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Example usage of the ScoutCache system
3
+ * This demonstrates how to use the cache-first loading pattern
4
+ */
5
+ import { useScoutRefresh } from "../hooks/useScoutRefresh";
6
+ import { scoutCache } from "./cache";
7
+ // Example 1: Basic usage with cache-first loading
8
+ export function ExampleBasicUsage() {
9
+ const { handleRefresh, getTimingStats, clearCache, getCacheStats } = useScoutRefresh({
10
+ autoRefresh: true,
11
+ cacheFirst: true,
12
+ cacheTtlMs: 10 * 60 * 1000, // 10 minutes
13
+ onRefreshComplete: () => {
14
+ console.log("Refresh completed!");
15
+ }
16
+ });
17
+ // Get timing stats
18
+ const stats = getTimingStats();
19
+ console.log("Performance stats:", stats);
20
+ // Get cache stats
21
+ const cacheStats = getCacheStats();
22
+ console.log("Cache stats:", cacheStats);
23
+ return {
24
+ handleRefresh,
25
+ clearCache,
26
+ stats: getTimingStats,
27
+ cacheStats: getCacheStats,
28
+ };
29
+ }
30
+ // Example 2: Advanced cache management
31
+ export async function ExampleAdvancedCacheManagement() {
32
+ // Check if cache is valid
33
+ const isValid = await scoutCache.isCacheValid(5 * 60 * 1000); // 5 minutes
34
+ console.log("Cache is valid:", isValid);
35
+ // Check if we should refresh
36
+ const shouldRefresh = await scoutCache.shouldRefresh(2 * 60 * 1000, // max age 2 minutes
37
+ false // not forcing refresh
38
+ );
39
+ console.log("Should refresh:", shouldRefresh);
40
+ // Get cache age
41
+ const age = await scoutCache.getCacheAge();
42
+ console.log("Cache age:", Math.round(age / 1000), "seconds");
43
+ // Invalidate cache
44
+ await scoutCache.invalidateHerdModules();
45
+ console.log("Cache invalidated");
46
+ // Clear all cache data
47
+ await scoutCache.clearHerdModules();
48
+ console.log("Cache cleared");
49
+ }
50
+ // Example 3: Background preloading
51
+ export async function ExampleBackgroundPreloading() {
52
+ // Simulate a function that loads herd modules
53
+ const loadHerdModules = async () => {
54
+ // This would be your actual API call
55
+ const response = await fetch("/api/herd-modules");
56
+ return response.json();
57
+ };
58
+ // Preload cache in background
59
+ await scoutCache.preloadCache(loadHerdModules, 15 * 60 * 1000); // 15 minutes TTL
60
+ console.log("Background preload completed");
61
+ }
62
+ // Example 4: Performance monitoring
63
+ export function ExamplePerformanceMonitoring() {
64
+ const { getTimingStats, getCacheStats } = useScoutRefresh({
65
+ cacheFirst: true,
66
+ onRefreshComplete: async () => {
67
+ const stats = getTimingStats();
68
+ const cacheStats = await getCacheStats();
69
+ console.log("=== Performance Report ===");
70
+ console.log("Total duration:", stats.totalDuration, "ms");
71
+ console.log("Cache load:", stats.cacheLoad, "ms");
72
+ console.log("API calls:", stats.herdModulesApi + stats.userApi, "ms");
73
+ console.log("Cache save:", stats.cacheSave, "ms");
74
+ console.log("Data processing:", stats.dataProcessing, "ms");
75
+ console.log("LocalStorage:", stats.localStorage, "ms");
76
+ console.log("Cache hit rate:", (cacheStats.hitRate * 100).toFixed(1) + "%");
77
+ console.log("Cache size:", cacheStats.size, "herd modules");
78
+ console.log("Cache age:", Math.round((Date.now() - cacheStats.lastUpdated) / 1000), "seconds");
79
+ }
80
+ });
81
+ return { getTimingStats, getCacheStats };
82
+ }
@@ -0,0 +1,52 @@
1
+ import { IHerdModule } from "../types/herd_module";
2
+ export interface CacheMetadata {
3
+ key: string;
4
+ timestamp: number;
5
+ ttl: number;
6
+ version: string;
7
+ etag?: string;
8
+ lastModified?: number;
9
+ }
10
+ export interface CacheResult<T> {
11
+ data: T | null;
12
+ isStale: boolean;
13
+ age: number;
14
+ metadata: CacheMetadata | null;
15
+ }
16
+ export interface CacheStats {
17
+ size: number;
18
+ lastUpdated: number;
19
+ isStale: boolean;
20
+ hitRate: number;
21
+ totalHits: number;
22
+ totalMisses: number;
23
+ }
24
+ export interface TimingStats {
25
+ totalDuration: number;
26
+ cacheLoad: number;
27
+ herdModulesApi: number;
28
+ userApi: number;
29
+ cacheSave: number;
30
+ dataProcessing: number;
31
+ localStorage: number;
32
+ }
33
+ export declare class ScoutCache {
34
+ private db;
35
+ private initPromise;
36
+ private stats;
37
+ private init;
38
+ setHerdModules(herdModules: IHerdModule[], ttlMs?: number, etag?: string): Promise<void>;
39
+ getHerdModules(): Promise<CacheResult<IHerdModule[]>>;
40
+ clearHerdModules(): Promise<void>;
41
+ invalidateHerdModules(): Promise<void>;
42
+ getCacheStats(): Promise<CacheStats>;
43
+ isCacheValid(ttlMs?: number): Promise<boolean>;
44
+ getCacheAge(): Promise<number>;
45
+ shouldRefresh(maxAgeMs?: number, forceRefresh?: boolean): Promise<{
46
+ shouldRefresh: boolean;
47
+ reason: string;
48
+ }>;
49
+ preloadCache(loadFunction: () => Promise<IHerdModule[]>, ttlMs?: number): Promise<void>;
50
+ getDefaultTtl(): number;
51
+ }
52
+ export declare const scoutCache: ScoutCache;
@@ -0,0 +1,213 @@
1
+ const DB_NAME = "ScoutCache";
2
+ const DB_VERSION = 1;
3
+ const HERD_MODULES_STORE = "herd_modules";
4
+ const CACHE_METADATA_STORE = "cache_metadata";
5
+ // Default TTL: 24 hours (1 day)
6
+ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
7
+ export class ScoutCache {
8
+ constructor() {
9
+ this.db = null;
10
+ this.initPromise = null;
11
+ this.stats = {
12
+ hits: 0,
13
+ misses: 0,
14
+ };
15
+ }
16
+ async init() {
17
+ if (this.db)
18
+ return;
19
+ if (this.initPromise)
20
+ return this.initPromise;
21
+ this.initPromise = new Promise((resolve, reject) => {
22
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
23
+ request.onerror = () => {
24
+ console.error("[ScoutCache] Failed to open IndexedDB:", request.error);
25
+ reject(request.error);
26
+ };
27
+ request.onsuccess = () => {
28
+ this.db = request.result;
29
+ console.log("[ScoutCache] IndexedDB initialized successfully");
30
+ resolve();
31
+ };
32
+ request.onupgradeneeded = (event) => {
33
+ const db = event.target.result;
34
+ // Create herd modules store
35
+ if (!db.objectStoreNames.contains(HERD_MODULES_STORE)) {
36
+ const herdModulesStore = db.createObjectStore(HERD_MODULES_STORE, { keyPath: "herdId" });
37
+ herdModulesStore.createIndex("timestamp", "timestamp", { unique: false });
38
+ }
39
+ // Create cache metadata store
40
+ if (!db.objectStoreNames.contains(CACHE_METADATA_STORE)) {
41
+ const metadataStore = db.createObjectStore(CACHE_METADATA_STORE, { keyPath: "key" });
42
+ }
43
+ console.log("[ScoutCache] Database schema upgraded");
44
+ };
45
+ });
46
+ return this.initPromise;
47
+ }
48
+ async setHerdModules(herdModules, ttlMs = DEFAULT_TTL_MS, etag) {
49
+ await this.init();
50
+ if (!this.db)
51
+ throw new Error("Database not initialized");
52
+ const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
53
+ return new Promise((resolve, reject) => {
54
+ transaction.onerror = () => reject(transaction.error);
55
+ transaction.oncomplete = () => resolve();
56
+ const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
57
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
58
+ const timestamp = Date.now();
59
+ const version = "1.0.0";
60
+ // Store each herd module
61
+ herdModules.forEach((herdModule) => {
62
+ const cacheEntry = {
63
+ herdId: herdModule.herd.id.toString(),
64
+ data: herdModule,
65
+ timestamp,
66
+ };
67
+ herdModulesStore.put(cacheEntry);
68
+ });
69
+ // Store cache metadata
70
+ const metadata = {
71
+ key: "herd_modules",
72
+ timestamp,
73
+ ttl: ttlMs,
74
+ version,
75
+ etag,
76
+ lastModified: timestamp,
77
+ };
78
+ metadataStore.put(metadata);
79
+ });
80
+ }
81
+ async getHerdModules() {
82
+ await this.init();
83
+ if (!this.db)
84
+ throw new Error("Database not initialized");
85
+ const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readonly");
86
+ return new Promise((resolve, reject) => {
87
+ transaction.onerror = () => reject(transaction.error);
88
+ const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
89
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
90
+ // Get metadata first
91
+ const metadataRequest = metadataStore.get("herd_modules");
92
+ metadataRequest.onsuccess = () => {
93
+ const metadata = metadataRequest.result;
94
+ const now = Date.now();
95
+ if (!metadata) {
96
+ this.stats.misses++;
97
+ resolve({ data: null, isStale: true, age: 0, metadata: null });
98
+ return;
99
+ }
100
+ const age = now - metadata.timestamp;
101
+ const isStale = age > metadata.ttl;
102
+ // Get all herd modules
103
+ const getAllRequest = herdModulesStore.getAll();
104
+ getAllRequest.onsuccess = () => {
105
+ const cacheEntries = getAllRequest.result;
106
+ const herdModules = cacheEntries
107
+ .map(entry => entry.data)
108
+ .sort((a, b) => a.herd.name.localeCompare(b.herd.name));
109
+ // Update stats
110
+ if (herdModules.length > 0) {
111
+ this.stats.hits++;
112
+ }
113
+ else {
114
+ this.stats.misses++;
115
+ }
116
+ resolve({
117
+ data: herdModules,
118
+ isStale,
119
+ age,
120
+ metadata,
121
+ });
122
+ };
123
+ };
124
+ });
125
+ }
126
+ async clearHerdModules() {
127
+ await this.init();
128
+ if (!this.db)
129
+ throw new Error("Database not initialized");
130
+ const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
131
+ return new Promise((resolve, reject) => {
132
+ transaction.onerror = () => reject(transaction.error);
133
+ transaction.oncomplete = () => resolve();
134
+ const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
135
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
136
+ herdModulesStore.clear();
137
+ metadataStore.delete("herd_modules");
138
+ });
139
+ }
140
+ async invalidateHerdModules() {
141
+ await this.init();
142
+ if (!this.db)
143
+ throw new Error("Database not initialized");
144
+ const transaction = this.db.transaction([CACHE_METADATA_STORE], "readwrite");
145
+ return new Promise((resolve, reject) => {
146
+ transaction.onerror = () => reject(transaction.error);
147
+ transaction.oncomplete = () => resolve();
148
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
149
+ metadataStore.delete("herd_modules");
150
+ });
151
+ }
152
+ async getCacheStats() {
153
+ const result = await this.getHerdModules();
154
+ const totalRequests = this.stats.hits + this.stats.misses;
155
+ const hitRate = totalRequests > 0 ? this.stats.hits / totalRequests : 0;
156
+ return {
157
+ size: result.data?.length || 0,
158
+ lastUpdated: result.data ? Date.now() - result.age : 0,
159
+ isStale: result.isStale,
160
+ hitRate: Math.round(hitRate * 100) / 100,
161
+ totalHits: this.stats.hits,
162
+ totalMisses: this.stats.misses,
163
+ };
164
+ }
165
+ async isCacheValid(ttlMs) {
166
+ const result = await this.getHerdModules();
167
+ if (!result.data || !result.metadata)
168
+ return false;
169
+ const effectiveTtl = ttlMs || result.metadata.ttl;
170
+ return !result.isStale && result.age < effectiveTtl;
171
+ }
172
+ async getCacheAge() {
173
+ const result = await this.getHerdModules();
174
+ return result.age;
175
+ }
176
+ // Method to check if we should refresh based on various conditions
177
+ async shouldRefresh(maxAgeMs, forceRefresh) {
178
+ if (forceRefresh) {
179
+ return { shouldRefresh: true, reason: "Force refresh requested" };
180
+ }
181
+ const result = await this.getHerdModules();
182
+ if (!result.data || result.data.length === 0) {
183
+ return { shouldRefresh: true, reason: "No cached data" };
184
+ }
185
+ if (result.isStale) {
186
+ return { shouldRefresh: true, reason: "Cache is stale" };
187
+ }
188
+ if (maxAgeMs && result.age > maxAgeMs) {
189
+ return { shouldRefresh: true, reason: `Cache age (${Math.round(result.age / 1000)}s) exceeds max age (${Math.round(maxAgeMs / 1000)}s)` };
190
+ }
191
+ return { shouldRefresh: false, reason: "Cache is valid and fresh" };
192
+ }
193
+ // Method to preload cache with background refresh
194
+ async preloadCache(loadFunction, ttlMs = DEFAULT_TTL_MS) {
195
+ try {
196
+ console.log("[ScoutCache] Starting background cache preload...");
197
+ const startTime = Date.now();
198
+ const herdModules = await loadFunction();
199
+ await this.setHerdModules(herdModules, ttlMs);
200
+ const duration = Date.now() - startTime;
201
+ console.log(`[ScoutCache] Background preload completed in ${duration}ms`);
202
+ }
203
+ catch (error) {
204
+ console.warn("[ScoutCache] Background preload failed:", error);
205
+ }
206
+ }
207
+ // Get the default TTL value
208
+ getDefaultTtl() {
209
+ return DEFAULT_TTL_MS;
210
+ }
211
+ }
212
+ // Singleton instance
213
+ export const scoutCache = new ScoutCache();
@@ -16,3 +16,4 @@ export * from "./ui";
16
16
  export * from "./users";
17
17
  export * from "./web";
18
18
  export * from "./zones";
19
+ export * from "./cache";
@@ -16,3 +16,4 @@ export * from "./ui";
16
16
  export * from "./users";
17
17
  export * from "./web";
18
18
  export * from "./zones";
19
+ export * from "./cache";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Example components demonstrating how to use data source tracking
3
+ */
4
+ export declare function DataSourceIndicator(): import("react/jsx-runtime").JSX.Element;
5
+ export declare function ConditionalContent(): import("react/jsx-runtime").JSX.Element;
6
+ export declare function PerformanceMetrics(): import("react/jsx-runtime").JSX.Element;
7
+ export declare function SmartRefreshButton(): import("react/jsx-runtime").JSX.Element;
8
+ export declare function HeaderWithDataSource(): import("react/jsx-runtime").JSX.Element;
9
+ export declare function DataSourceDebugger(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useDataSource, useDataSourceDescription } from './useDataSource';
3
+ import { useScoutRefresh } from './useScoutRefresh';
4
+ // Example 1: Basic data source display
5
+ export function DataSourceIndicator() {
6
+ const { dataSource, isFromCache, cacheAge, isStale } = useDataSource();
7
+ const description = useDataSourceDescription();
8
+ return (_jsxs("div", { className: "data-source-indicator", children: [_jsx("div", { className: `source-badge ${dataSource.toLowerCase()}`, children: dataSource }), _jsxs("div", { className: "source-details", children: [_jsx("p", { children: description }), isFromCache && (_jsxs("div", { className: "cache-info", children: [_jsxs("span", { children: ["Cache age: ", Math.round(cacheAge / 1000), "s"] }), _jsx("span", { className: isStale ? 'stale' : 'fresh', children: isStale ? 'Stale' : 'Fresh' })] }))] })] }));
9
+ }
10
+ // Example 2: Conditional rendering based on data source
11
+ export function ConditionalContent() {
12
+ const { isFromCache, isFromDatabase, isStale } = useDataSource();
13
+ return (_jsxs("div", { children: [isFromCache && (_jsx("div", { className: "cache-notice", children: isStale ? (_jsx("p", { children: "\u26A0\uFE0F Showing cached data (may be outdated)" })) : (_jsx("p", { children: "\u2705 Showing fresh cached data" })) })), isFromDatabase && (_jsx("div", { className: "database-notice", children: _jsx("p", { children: "\uD83D\uDD04 Showing live data from database" }) }))] }));
14
+ }
15
+ // Example 3: Performance metrics with data source
16
+ export function PerformanceMetrics() {
17
+ const { dataSource, cacheAge } = useDataSource();
18
+ const { getTimingStats, getCacheStats } = useScoutRefresh();
19
+ const handleShowMetrics = () => {
20
+ const timing = getTimingStats();
21
+ console.log('Performance Metrics:', {
22
+ dataSource,
23
+ cacheAge: cacheAge ? Math.round(cacheAge / 1000) : 'N/A',
24
+ totalDuration: timing.totalDuration,
25
+ cacheLoad: timing.cacheLoad,
26
+ apiCalls: timing.herdModulesApi + timing.userApi,
27
+ });
28
+ };
29
+ return (_jsxs("div", { className: "performance-metrics", children: [_jsx("h3", { children: "Performance Metrics" }), _jsxs("p", { children: ["Data Source: ", dataSource] }), cacheAge && _jsxs("p", { children: ["Cache Age: ", Math.round(cacheAge / 1000), "s"] }), _jsx("button", { onClick: handleShowMetrics, children: "Show Detailed Metrics" })] }));
30
+ }
31
+ // Example 4: Data source aware refresh button
32
+ export function SmartRefreshButton() {
33
+ const { isFromCache, isStale, cacheAge } = useDataSource();
34
+ const { handleRefresh } = useScoutRefresh();
35
+ const getButtonText = () => {
36
+ if (isFromCache) {
37
+ if (isStale) {
38
+ return 'Refresh Stale Data';
39
+ }
40
+ else {
41
+ const ageSeconds = Math.round(cacheAge / 1000);
42
+ return `Refresh (${ageSeconds}s old)`;
43
+ }
44
+ }
45
+ return 'Refresh Data';
46
+ };
47
+ const getButtonStyle = () => {
48
+ if (isFromCache && isStale) {
49
+ return 'refresh-button stale';
50
+ }
51
+ else if (isFromCache) {
52
+ return 'refresh-button cached';
53
+ }
54
+ return 'refresh-button fresh';
55
+ };
56
+ return (_jsx("button", { className: getButtonStyle(), onClick: handleRefresh, children: getButtonText() }));
57
+ }
58
+ // Example 5: Data source status in header
59
+ export function HeaderWithDataSource() {
60
+ const description = useDataSourceDescription();
61
+ const { handleRefresh } = useScoutRefresh();
62
+ return (_jsxs("header", { className: "app-header", children: [_jsx("h1", { children: "Scout Dashboard" }), _jsxs("div", { className: "header-controls", children: [_jsx("span", { className: "data-source-status", children: description }), _jsx("button", { onClick: handleRefresh, children: "Refresh" })] })] }));
63
+ }
64
+ // Example 6: Data source debugging component
65
+ export function DataSourceDebugger() {
66
+ const { dataSource, dataSourceInfo, isFromCache, isFromDatabase, isUnknown } = useDataSource();
67
+ return (_jsxs("div", { className: "data-source-debugger", children: [_jsx("h3", { children: "Data Source Debug Info" }), _jsxs("div", { className: "debug-info", children: [_jsxs("p", { children: [_jsx("strong", { children: "Source:" }), " ", dataSource] }), _jsxs("p", { children: [_jsx("strong", { children: "From Cache:" }), " ", isFromCache ? 'Yes' : 'No'] }), _jsxs("p", { children: [_jsx("strong", { children: "From Database:" }), " ", isFromDatabase ? 'Yes' : 'No'] }), _jsxs("p", { children: [_jsx("strong", { children: "Unknown:" }), " ", isUnknown ? 'Yes' : 'No'] }), dataSourceInfo && (_jsxs("div", { className: "detailed-info", children: [_jsxs("p", { children: [_jsx("strong", { children: "Timestamp:" }), " ", new Date(dataSourceInfo.timestamp).toISOString()] }), dataSourceInfo.cacheAge && (_jsxs("p", { children: [_jsx("strong", { children: "Cache Age:" }), " ", Math.round(dataSourceInfo.cacheAge / 1000), "s"] })), typeof dataSourceInfo.isStale === 'boolean' && (_jsxs("p", { children: [_jsx("strong", { children: "Is Stale:" }), " ", dataSourceInfo.isStale ? 'Yes' : 'No'] }))] }))] })] }));
68
+ }
@@ -0,0 +1,43 @@
1
+ import { EnumDataSource, IDataSourceInfo } from "../types/data_source";
2
+ /**
3
+ * Hook to access data source information from the Redux store
4
+ *
5
+ * @returns Object containing:
6
+ * - dataSource: The current data source (CACHE, DATABASE, or UNKNOWN)
7
+ * - dataSourceInfo: Detailed information about the data source
8
+ * - isFromCache: Boolean indicating if data is from cache
9
+ * - isFromDatabase: Boolean indicating if data is from database
10
+ * - cacheAge: Age of cached data in milliseconds (if from cache)
11
+ * - isStale: Whether cached data is stale (if from cache)
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function MyComponent() {
16
+ * const { dataSource, isFromCache, cacheAge, isStale } = useDataSource();
17
+ *
18
+ * return (
19
+ * <div>
20
+ * <p>Data source: {dataSource}</p>
21
+ * {isFromCache && (
22
+ * <p>Cache age: {Math.round(cacheAge! / 1000)}s, Stale: {isStale ? 'Yes' : 'No'}</p>
23
+ * )}
24
+ * </div>
25
+ * );
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function useDataSource(): {
30
+ dataSource: EnumDataSource;
31
+ dataSourceInfo: IDataSourceInfo | null;
32
+ isFromCache: boolean;
33
+ isFromDatabase: boolean;
34
+ isUnknown: boolean;
35
+ cacheAge: number | undefined;
36
+ isStale: boolean | undefined;
37
+ };
38
+ /**
39
+ * Hook to get a human-readable description of the data source
40
+ *
41
+ * @returns String description of the current data source
42
+ */
43
+ export declare function useDataSourceDescription(): string;
@@ -0,0 +1,63 @@
1
+ import { useSelector } from "react-redux";
2
+ import { EnumDataSource } from "../types/data_source";
3
+ /**
4
+ * Hook to access data source information from the Redux store
5
+ *
6
+ * @returns Object containing:
7
+ * - dataSource: The current data source (CACHE, DATABASE, or UNKNOWN)
8
+ * - dataSourceInfo: Detailed information about the data source
9
+ * - isFromCache: Boolean indicating if data is from cache
10
+ * - isFromDatabase: Boolean indicating if data is from database
11
+ * - cacheAge: Age of cached data in milliseconds (if from cache)
12
+ * - isStale: Whether cached data is stale (if from cache)
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * function MyComponent() {
17
+ * const { dataSource, isFromCache, cacheAge, isStale } = useDataSource();
18
+ *
19
+ * return (
20
+ * <div>
21
+ * <p>Data source: {dataSource}</p>
22
+ * {isFromCache && (
23
+ * <p>Cache age: {Math.round(cacheAge! / 1000)}s, Stale: {isStale ? 'Yes' : 'No'}</p>
24
+ * )}
25
+ * </div>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+ export function useDataSource() {
31
+ const dataSource = useSelector((state) => state.scout.data_source);
32
+ const dataSourceInfo = useSelector((state) => state.scout.data_source_info);
33
+ const isFromCache = dataSource === EnumDataSource.CACHE;
34
+ const isFromDatabase = dataSource === EnumDataSource.DATABASE;
35
+ const isUnknown = dataSource === EnumDataSource.UNKNOWN;
36
+ return {
37
+ dataSource,
38
+ dataSourceInfo,
39
+ isFromCache,
40
+ isFromDatabase,
41
+ isUnknown,
42
+ cacheAge: dataSourceInfo?.cacheAge,
43
+ isStale: dataSourceInfo?.isStale,
44
+ };
45
+ }
46
+ /**
47
+ * Hook to get a human-readable description of the data source
48
+ *
49
+ * @returns String description of the current data source
50
+ */
51
+ export function useDataSourceDescription() {
52
+ const { dataSource, cacheAge, isStale } = useDataSource();
53
+ switch (dataSource) {
54
+ case EnumDataSource.CACHE:
55
+ const ageSeconds = cacheAge ? Math.round(cacheAge / 1000) : 0;
56
+ return `Loaded from cache (${ageSeconds}s old${isStale ? ', stale' : ', fresh'})`;
57
+ case EnumDataSource.DATABASE:
58
+ return 'Loaded from database (fresh data)';
59
+ case EnumDataSource.UNKNOWN:
60
+ default:
61
+ return 'Data source unknown';
62
+ }
63
+ }
@@ -1,21 +1,31 @@
1
+ import { CacheStats, TimingStats } from "../helpers/cache";
1
2
  export interface UseScoutRefreshOptions {
2
3
  autoRefresh?: boolean;
3
4
  onRefreshComplete?: () => void;
5
+ cacheFirst?: boolean;
6
+ cacheTtlMs?: number;
4
7
  }
5
8
  /**
6
- * Hook for refreshing scout data with detailed timing measurements
9
+ * Hook for refreshing scout data with detailed timing measurements and cache-first loading
7
10
  *
8
11
  * @param options - Configuration options for the refresh behavior
9
12
  * @param options.autoRefresh - Whether to automatically refresh on mount (default: true)
10
13
  * @param options.onRefreshComplete - Callback function called when refresh completes
14
+ * @param options.cacheFirst - Whether to load from cache first, then refresh (default: true)
15
+ * @param options.cacheTtlMs - Cache time-to-live in milliseconds (default: 24 hours)
11
16
  *
12
17
  * @returns Object containing:
13
18
  * - handleRefresh: Function to manually trigger a refresh
14
19
  * - getTimingStats: Function to get detailed timing statistics for the last refresh
20
+ * - clearCache: Function to clear the cache
21
+ * - getCacheStats: Function to get cache statistics
15
22
  *
16
23
  * @example
17
24
  * ```tsx
18
- * const { handleRefresh, getTimingStats } = useScoutRefresh();
25
+ * const { handleRefresh, getTimingStats, clearCache, getCacheStats } = useScoutRefresh({
26
+ * cacheFirst: true,
27
+ * cacheTtlMs: 10 * 60 * 1000 // 10 minutes
28
+ * });
19
29
  *
20
30
  * // Get timing stats after a refresh
21
31
  * const stats = getTimingStats();
@@ -28,11 +38,7 @@ export interface UseScoutRefreshOptions {
28
38
  */
29
39
  export declare function useScoutRefresh(options?: UseScoutRefreshOptions): {
30
40
  handleRefresh: () => Promise<void>;
31
- getTimingStats: () => {
32
- totalDuration: number;
33
- herdModulesApi: number;
34
- userApi: number;
35
- dataProcessing: number;
36
- localStorage: number;
37
- };
41
+ getTimingStats: () => TimingStats;
42
+ clearCache: () => Promise<void>;
43
+ getCacheStats: () => Promise<CacheStats>;
38
44
  };
@@ -1,24 +1,32 @@
1
1
  import { useEffect, useCallback, useRef } from "react";
2
2
  import { useAppDispatch } from "../store/hooks";
3
- import { EnumScoutStateStatus, setActiveHerdId, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setLocalStorageDuration, setUser, } from "../store/scout";
3
+ import { EnumScoutStateStatus, setActiveHerdId, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setLocalStorageDuration, setUser, setDataSource, setDataSourceInfo, } from "../store/scout";
4
4
  import { EnumHerdModulesLoadingState } from "../types/herd_module";
5
5
  import { server_load_herd_modules } from "../helpers/herds";
6
6
  import { server_get_user } from "../helpers/users";
7
- import { EnumWebResponse } from "../types/requests";
7
+ import { scoutCache } from "../helpers/cache";
8
+ import { EnumDataSource } from "../types/data_source";
8
9
  /**
9
- * Hook for refreshing scout data with detailed timing measurements
10
+ * Hook for refreshing scout data with detailed timing measurements and cache-first loading
10
11
  *
11
12
  * @param options - Configuration options for the refresh behavior
12
13
  * @param options.autoRefresh - Whether to automatically refresh on mount (default: true)
13
14
  * @param options.onRefreshComplete - Callback function called when refresh completes
15
+ * @param options.cacheFirst - Whether to load from cache first, then refresh (default: true)
16
+ * @param options.cacheTtlMs - Cache time-to-live in milliseconds (default: 24 hours)
14
17
  *
15
18
  * @returns Object containing:
16
19
  * - handleRefresh: Function to manually trigger a refresh
17
20
  * - getTimingStats: Function to get detailed timing statistics for the last refresh
21
+ * - clearCache: Function to clear the cache
22
+ * - getCacheStats: Function to get cache statistics
18
23
  *
19
24
  * @example
20
25
  * ```tsx
21
- * const { handleRefresh, getTimingStats } = useScoutRefresh();
26
+ * const { handleRefresh, getTimingStats, clearCache, getCacheStats } = useScoutRefresh({
27
+ * cacheFirst: true,
28
+ * cacheTtlMs: 10 * 60 * 1000 // 10 minutes
29
+ * });
22
30
  *
23
31
  * // Get timing stats after a refresh
24
32
  * const stats = getTimingStats();
@@ -30,7 +38,8 @@ import { EnumWebResponse } from "../types/requests";
30
38
  * ```
31
39
  */
32
40
  export function useScoutRefresh(options = {}) {
33
- const { autoRefresh = true, onRefreshComplete } = options;
41
+ const { autoRefresh = true, onRefreshComplete, cacheFirst = true, cacheTtlMs = 24 * 60 * 60 * 1000, // 24 hours default (1 day)
42
+ } = options;
34
43
  const dispatch = useAppDispatch();
35
44
  const refreshInProgressRef = useRef(false);
36
45
  // Refs to store timing measurements
@@ -40,6 +49,8 @@ export function useScoutRefresh(options = {}) {
40
49
  userApiDuration: 0,
41
50
  dataProcessingDuration: 0,
42
51
  localStorageDuration: 0,
52
+ cacheLoadDuration: 0,
53
+ cacheSaveDuration: 0,
43
54
  });
44
55
  const handleRefresh = useCallback(async () => {
45
56
  // Prevent concurrent refresh calls
@@ -53,14 +64,66 @@ export function useScoutRefresh(options = {}) {
53
64
  try {
54
65
  dispatch(setStatus(EnumScoutStateStatus.LOADING));
55
66
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.LOADING));
56
- // Run API requests in parallel for better performance
57
- console.log("[useScoutRefresh] Starting parallel API requests...");
67
+ let cachedHerdModules = null;
68
+ let cacheLoadDuration = 0;
69
+ // Step 1: Load from cache first if enabled
70
+ if (cacheFirst) {
71
+ const cacheStartTime = Date.now();
72
+ try {
73
+ console.log("[useScoutRefresh] Loading from cache...");
74
+ const cacheResult = await scoutCache.getHerdModules();
75
+ cacheLoadDuration = Date.now() - cacheStartTime;
76
+ timingRefs.current.cacheLoadDuration = cacheLoadDuration;
77
+ if (cacheResult.data && cacheResult.data.length > 0) {
78
+ cachedHerdModules = cacheResult.data;
79
+ console.log(`[useScoutRefresh] Loaded ${cachedHerdModules.length} herd modules from cache in ${cacheLoadDuration}ms (age: ${Math.round(cacheResult.age / 1000)}s, stale: ${cacheResult.isStale})`);
80
+ // Set data source to CACHE
81
+ dispatch(setDataSource(EnumDataSource.CACHE));
82
+ dispatch(setDataSourceInfo({
83
+ source: EnumDataSource.CACHE,
84
+ timestamp: Date.now(),
85
+ cacheAge: cacheResult.age,
86
+ isStale: cacheResult.isStale,
87
+ }));
88
+ // Immediately update the store with cached data
89
+ dispatch(setHerdModules(cachedHerdModules));
90
+ dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
91
+ // If cache is fresh, we can return early
92
+ if (!cacheResult.isStale) {
93
+ console.log("[useScoutRefresh] Cache is fresh, skipping API call");
94
+ // Still need to load user data
95
+ const userStartTime = Date.now();
96
+ const res_new_user = await server_get_user();
97
+ const userApiDuration = Date.now() - userStartTime;
98
+ timingRefs.current.userApiDuration = userApiDuration;
99
+ dispatch(setUserApiDuration(userApiDuration));
100
+ if (res_new_user && res_new_user.data) {
101
+ dispatch(setUser(res_new_user.data));
102
+ }
103
+ const totalDuration = Date.now() - startTime;
104
+ dispatch(setHerdModulesLoadedInMs(totalDuration));
105
+ dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
106
+ console.log(`[useScoutRefresh] Cache-first refresh completed in ${totalDuration}ms`);
107
+ onRefreshComplete?.();
108
+ return;
109
+ }
110
+ }
111
+ else {
112
+ console.log("[useScoutRefresh] No cached data found");
113
+ }
114
+ }
115
+ catch (cacheError) {
116
+ console.warn("[useScoutRefresh] Cache load failed:", cacheError);
117
+ // Continue with API call
118
+ }
119
+ }
120
+ // Step 2: Load fresh data from API
121
+ console.log("[useScoutRefresh] Loading fresh data from API...");
58
122
  const parallelStartTime = Date.now();
59
123
  const [herdModulesResult, userResult] = await Promise.all([
60
124
  (async () => {
61
125
  const start = Date.now();
62
126
  console.log(`[useScoutRefresh] Starting herd modules request at ${new Date(start).toISOString()}`);
63
- // High priority request with optimization
64
127
  const result = await server_load_herd_modules();
65
128
  const duration = Date.now() - start;
66
129
  console.log(`[useScoutRefresh] Herd modules request completed in ${duration}ms`);
@@ -69,7 +132,6 @@ export function useScoutRefresh(options = {}) {
69
132
  (async () => {
70
133
  const start = Date.now();
71
134
  console.log(`[useScoutRefresh] Starting user request at ${new Date(start).toISOString()}`);
72
- // High priority request with optimization
73
135
  const result = await server_get_user();
74
136
  const duration = Date.now() - start;
75
137
  console.log(`[useScoutRefresh] User request completed in ${duration}ms`);
@@ -83,43 +145,12 @@ export function useScoutRefresh(options = {}) {
83
145
  const res_new_user = userResult.result;
84
146
  const herdModulesDuration = herdModulesResult.duration;
85
147
  const userApiDuration = userResult.duration;
86
- // Calculate request timing breakdown
87
- const requestStartTime = parallelStartTime;
88
- const requestEndTime = Date.now();
89
- const totalRequestTime = requestEndTime - requestStartTime;
90
- console.log(`[useScoutRefresh] Request timing breakdown:`);
91
- console.log(` - Request started at: ${new Date(requestStartTime).toISOString()}`);
92
- console.log(` - Request completed at: ${new Date(requestEndTime).toISOString()}`);
93
- console.log(` - Total request time: ${totalRequestTime}ms`);
94
- console.log(` - Parallel execution time: ${parallelDuration}ms`);
95
- console.log(` - Request overhead: ${totalRequestTime - parallelDuration}ms`);
96
- // Calculate network latency for herd modules
97
- let networkLatencyMs = 0;
98
- if (herdModulesResponse.status === EnumWebResponse.SUCCESS &&
99
- herdModulesResponse.data) {
100
- const serverFinishTime = herdModulesResponse.time_finished;
101
- const clientReceiveTime = Date.now();
102
- const estimatedNetworkLatency = clientReceiveTime - serverFinishTime;
103
- networkLatencyMs = Math.max(0, estimatedNetworkLatency);
104
- console.log(`[useScoutRefresh] Herd modules performance:`);
105
- console.log(` - Server processing: ${herdModulesResponse.server_processing_time_ms}ms`);
106
- console.log(` - Network latency: ${networkLatencyMs}ms`);
107
- console.log(` - Total client time: ${herdModulesDuration}ms`);
108
- }
109
148
  // Store timing values
110
149
  timingRefs.current.herdModulesDuration = herdModulesDuration;
111
150
  timingRefs.current.userApiDuration = userApiDuration;
112
151
  // Dispatch timing actions
113
152
  dispatch(setHerdModulesApiDuration(herdModulesDuration));
114
153
  dispatch(setUserApiDuration(userApiDuration));
115
- // Calculate network overhead
116
- const totalApiTime = herdModulesDuration + userApiDuration;
117
- const networkOverhead = parallelDuration - Math.max(herdModulesDuration, userApiDuration);
118
- console.log(`[useScoutRefresh] API performance:`);
119
- console.log(` - Herd modules: ${herdModulesDuration}ms`);
120
- console.log(` - User API: ${userApiDuration}ms`);
121
- console.log(` - Parallel execution: ${parallelDuration}ms`);
122
- console.log(` - Time saved with parallel: ${totalApiTime - parallelDuration}ms`);
123
154
  // Validate API responses
124
155
  const validationStartTime = Date.now();
125
156
  if (!herdModulesResponse.data ||
@@ -133,7 +164,24 @@ export function useScoutRefresh(options = {}) {
133
164
  console.log(`[useScoutRefresh] Data validation took: ${validationDuration}ms`);
134
165
  // Use the validated data
135
166
  const compatible_new_herd_modules = herdModulesResponse.data;
136
- // Measure data processing duration
167
+ // Set data source to DATABASE
168
+ dispatch(setDataSource(EnumDataSource.DATABASE));
169
+ dispatch(setDataSourceInfo({
170
+ source: EnumDataSource.DATABASE,
171
+ timestamp: Date.now(),
172
+ }));
173
+ // Step 3: Update cache with fresh data
174
+ const cacheSaveStartTime = Date.now();
175
+ try {
176
+ await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs);
177
+ const cacheSaveDuration = Date.now() - cacheSaveStartTime;
178
+ timingRefs.current.cacheSaveDuration = cacheSaveDuration;
179
+ console.log(`[useScoutRefresh] Cache updated in ${cacheSaveDuration}ms with TTL: ${Math.round(cacheTtlMs / 1000)}s`);
180
+ }
181
+ catch (cacheError) {
182
+ console.warn("[useScoutRefresh] Cache save failed:", cacheError);
183
+ }
184
+ // Step 4: Update store with fresh data
137
185
  const dataProcessingStartTime = Date.now();
138
186
  dispatch(setHerdModules(compatible_new_herd_modules));
139
187
  dispatch(setUser(res_new_user.data));
@@ -141,9 +189,8 @@ export function useScoutRefresh(options = {}) {
141
189
  const dataProcessingDuration = Date.now() - dataProcessingStartTime;
142
190
  timingRefs.current.dataProcessingDuration = dataProcessingDuration;
143
191
  dispatch(setDataProcessingDuration(dataProcessingDuration));
144
- // Measure localStorage operations duration
192
+ // Step 5: Handle localStorage operations
145
193
  const localStorageStartTime = Date.now();
146
- // Safely handle localStorage operations
147
194
  try {
148
195
  // Check local storage for a last selected herd
149
196
  const lastSelectedHerd = localStorage.getItem("last_selected_herd");
@@ -177,15 +224,24 @@ export function useScoutRefresh(options = {}) {
177
224
  // Log essential performance metrics
178
225
  console.log(`[useScoutRefresh] Refresh completed successfully:`);
179
226
  console.log(` - Total duration: ${loadingDuration}ms`);
180
- console.log(` - Herd modules: ${herdModulesDuration}ms`);
227
+ console.log(` - Cache load: ${cacheLoadDuration}ms`);
228
+ console.log(` - Herd modules API: ${herdModulesDuration}ms`);
181
229
  console.log(` - User API: ${userApiDuration}ms`);
182
- console.log(` - Parallel execution: ${parallelDuration}ms`);
183
- console.log(` - Time saved with parallel: ${totalApiTime - parallelDuration}ms`);
230
+ console.log(` - Cache save: ${timingRefs.current.cacheSaveDuration}ms`);
231
+ console.log(` - Data processing: ${dataProcessingDuration}ms`);
232
+ console.log(` - LocalStorage: ${localStorageDuration}ms`);
233
+ console.log(` - Cache TTL: ${Math.round(cacheTtlMs / 1000)}s`);
184
234
  onRefreshComplete?.();
185
235
  }
186
236
  catch (error) {
187
237
  const loadingDuration = Date.now() - startTime;
188
238
  console.error("Error refreshing scout data:", error);
239
+ // Set data source to UNKNOWN on error
240
+ dispatch(setDataSource(EnumDataSource.UNKNOWN));
241
+ dispatch(setDataSourceInfo({
242
+ source: EnumDataSource.UNKNOWN,
243
+ timestamp: Date.now(),
244
+ }));
189
245
  // Ensure consistent state updates on error
190
246
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.UNSUCCESSFULLY_LOADED));
191
247
  dispatch(setHerdModulesLoadedInMs(loadingDuration));
@@ -199,7 +255,7 @@ export function useScoutRefresh(options = {}) {
199
255
  finally {
200
256
  refreshInProgressRef.current = false;
201
257
  }
202
- }, [dispatch, onRefreshComplete]);
258
+ }, [dispatch, onRefreshComplete, cacheFirst, cacheTtlMs]);
203
259
  useEffect(() => {
204
260
  if (autoRefresh) {
205
261
  handleRefresh();
@@ -211,14 +267,45 @@ export function useScoutRefresh(options = {}) {
211
267
  const startTime = timingRefs.current.startTime;
212
268
  return {
213
269
  totalDuration: startTime > 0 ? now - startTime : 0,
270
+ cacheLoad: timingRefs.current.cacheLoadDuration,
214
271
  herdModulesApi: timingRefs.current.herdModulesDuration,
215
272
  userApi: timingRefs.current.userApiDuration,
273
+ cacheSave: timingRefs.current.cacheSaveDuration,
216
274
  dataProcessing: timingRefs.current.dataProcessingDuration,
217
275
  localStorage: timingRefs.current.localStorageDuration,
218
276
  };
219
277
  }, []);
278
+ // Utility function to clear cache
279
+ const clearCache = useCallback(async () => {
280
+ try {
281
+ await scoutCache.clearHerdModules();
282
+ console.log("[useScoutRefresh] Cache cleared successfully");
283
+ }
284
+ catch (error) {
285
+ console.error("[useScoutRefresh] Failed to clear cache:", error);
286
+ }
287
+ }, []);
288
+ // Utility function to get cache statistics
289
+ const getCacheStats = useCallback(async () => {
290
+ try {
291
+ return await scoutCache.getCacheStats();
292
+ }
293
+ catch (error) {
294
+ console.error("[useScoutRefresh] Failed to get cache stats:", error);
295
+ return {
296
+ size: 0,
297
+ lastUpdated: 0,
298
+ isStale: true,
299
+ hitRate: 0,
300
+ totalHits: 0,
301
+ totalMisses: 0,
302
+ };
303
+ }
304
+ }, []);
220
305
  return {
221
306
  handleRefresh,
222
307
  getTimingStats,
308
+ clearCache,
309
+ getCacheStats,
223
310
  };
224
311
  }
@@ -1,5 +1,6 @@
1
1
  import { IUser } from "../types/db";
2
2
  import { IHerdModule, EnumHerdModulesLoadingState } from "../types/herd_module";
3
+ import { EnumDataSource, IDataSourceInfo } from "../types/data_source";
3
4
  export declare enum EnumScoutStateStatus {
4
5
  LOADING = "LOADING",
5
6
  DONE_LOADING = "DONE_LOADING"
@@ -17,6 +18,11 @@ export interface ScoutState {
17
18
  active_device_id: string | null;
18
19
  lastRefreshed: number;
19
20
  user: IUser | null;
21
+ data_source: EnumDataSource;
22
+ data_source_info: IDataSourceInfo | null;
23
+ }
24
+ export interface RootState {
25
+ scout: ScoutState;
20
26
  }
21
27
  export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
22
28
  setHerdModules: (state: import("immer").WritableDraft<ScoutState>, action: {
@@ -59,6 +65,14 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
59
65
  payload: any;
60
66
  type: string;
61
67
  }) => void;
68
+ setDataSource: (state: import("immer").WritableDraft<ScoutState>, action: {
69
+ payload: any;
70
+ type: string;
71
+ }) => void;
72
+ setDataSourceInfo: (state: import("immer").WritableDraft<ScoutState>, action: {
73
+ payload: any;
74
+ type: string;
75
+ }) => void;
62
76
  replaceEventsForHerdModule: (state: import("immer").WritableDraft<ScoutState>, action: {
63
77
  payload: any;
64
78
  type: string;
@@ -140,6 +154,6 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
140
154
  type: string;
141
155
  }) => void;
142
156
  }, "scout", "scout", import("@reduxjs/toolkit").SliceSelectors<ScoutState>>;
143
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setLocalStorageDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setLocalStorageDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">;
157
+ export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setLocalStorageDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setLocalStorageDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">;
144
158
  declare const _default: import("redux").Reducer<ScoutState>;
145
159
  export default _default;
@@ -1,5 +1,6 @@
1
1
  import { createSlice } from "@reduxjs/toolkit";
2
2
  import { EnumHerdModulesLoadingState } from "../types/herd_module";
3
+ import { EnumDataSource } from "../types/data_source";
3
4
  export var EnumScoutStateStatus;
4
5
  (function (EnumScoutStateStatus) {
5
6
  EnumScoutStateStatus["LOADING"] = "LOADING";
@@ -19,6 +20,9 @@ const initialState = {
19
20
  active_herd_id: null,
20
21
  active_device_id: null,
21
22
  user: null,
23
+ // Initialize data source tracking
24
+ data_source: EnumDataSource.UNKNOWN,
25
+ data_source_info: null,
22
26
  };
23
27
  export const scoutSlice = createSlice({
24
28
  name: "scout",
@@ -55,6 +59,12 @@ export const scoutSlice = createSlice({
55
59
  setActiveDeviceId: (state, action) => {
56
60
  state.active_device_id = action.payload;
57
61
  },
62
+ setDataSource: (state, action) => {
63
+ state.data_source = action.payload;
64
+ },
65
+ setDataSourceInfo: (state, action) => {
66
+ state.data_source_info = action.payload;
67
+ },
58
68
  replaceEventsForHerdModule: (state, action) => {
59
69
  const { herd_id, events } = action.payload;
60
70
  const herd_module = state.herd_modules.find((hm) => hm.herd.id.toString() === herd_id);
@@ -254,5 +264,5 @@ export const scoutSlice = createSlice({
254
264
  },
255
265
  });
256
266
  // Action creators are generated for each case reducer function
257
- export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setLocalStorageDuration, setActiveHerdId, setActiveDeviceId, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, } = scoutSlice.actions;
267
+ export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setLocalStorageDuration, setActiveHerdId, setActiveDeviceId, setDataSource, setDataSourceInfo, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, } = scoutSlice.actions;
258
268
  export default scoutSlice.reducer;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Enum representing the source of data in the Scout application
3
+ */
4
+ export declare enum EnumDataSource {
5
+ CACHE = "CACHE",
6
+ DATABASE = "DATABASE",
7
+ UNKNOWN = "UNKNOWN"
8
+ }
9
+ /**
10
+ * Interface for data source information
11
+ */
12
+ export interface IDataSourceInfo {
13
+ source: EnumDataSource;
14
+ timestamp: number;
15
+ cacheAge?: number;
16
+ isStale?: boolean;
17
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Enum representing the source of data in the Scout application
3
+ */
4
+ export var EnumDataSource;
5
+ (function (EnumDataSource) {
6
+ EnumDataSource["CACHE"] = "CACHE";
7
+ EnumDataSource["DATABASE"] = "DATABASE";
8
+ EnumDataSource["UNKNOWN"] = "UNKNOWN";
9
+ })(EnumDataSource || (EnumDataSource = {}));
@@ -5,3 +5,4 @@ export * from "./requests";
5
5
  export * from "./gps";
6
6
  export * from "./supabase";
7
7
  export * from "./events";
8
+ export * from "./data_source";
@@ -5,3 +5,4 @@ export * from "./requests";
5
5
  export * from "./gps";
6
6
  export * from "./supabase";
7
7
  export * from "./events";
8
+ export * from "./data_source";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.77",
3
+ "version": "1.0.78",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",