@adventurelabs/scout-core 1.0.84 → 1.0.86
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.
- package/dist/helpers/cache.d.ts +10 -0
- package/dist/helpers/cache.js +160 -13
- package/dist/hooks/useScoutRefresh.d.ts +5 -1
- package/dist/hooks/useScoutRefresh.js +87 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/db.d.ts +1 -0
- package/dist/types/herd_module.d.ts +4 -2
- package/dist/types/herd_module.js +23 -6
- package/package.json +1 -1
package/dist/helpers/cache.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface CacheMetadata {
|
|
|
4
4
|
timestamp: number;
|
|
5
5
|
ttl: number;
|
|
6
6
|
version: string;
|
|
7
|
+
dbVersion: number;
|
|
7
8
|
etag?: string;
|
|
8
9
|
lastModified?: number;
|
|
9
10
|
}
|
|
@@ -30,11 +31,16 @@ export interface TimingStats {
|
|
|
30
31
|
dataProcessing: number;
|
|
31
32
|
localStorage: number;
|
|
32
33
|
}
|
|
34
|
+
export interface DatabaseHealth {
|
|
35
|
+
healthy: boolean;
|
|
36
|
+
issues: string[];
|
|
37
|
+
}
|
|
33
38
|
export declare class ScoutCache {
|
|
34
39
|
private db;
|
|
35
40
|
private initPromise;
|
|
36
41
|
private stats;
|
|
37
42
|
private init;
|
|
43
|
+
private validateDatabaseSchema;
|
|
38
44
|
setHerdModules(herdModules: IHerdModule[], ttlMs?: number, etag?: string): Promise<void>;
|
|
39
45
|
getHerdModules(): Promise<CacheResult<IHerdModule[]>>;
|
|
40
46
|
clearHerdModules(): Promise<void>;
|
|
@@ -48,5 +54,9 @@ export declare class ScoutCache {
|
|
|
48
54
|
}>;
|
|
49
55
|
preloadCache(loadFunction: () => Promise<IHerdModule[]>, ttlMs?: number): Promise<void>;
|
|
50
56
|
getDefaultTtl(): number;
|
|
57
|
+
getCurrentDbVersion(): number;
|
|
58
|
+
isCacheVersionCompatible(): Promise<boolean>;
|
|
59
|
+
resetDatabase(): Promise<void>;
|
|
60
|
+
checkDatabaseHealth(): Promise<DatabaseHealth>;
|
|
51
61
|
}
|
|
52
62
|
export declare const scoutCache: ScoutCache;
|
package/dist/helpers/cache.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const DB_NAME = "ScoutCache";
|
|
2
|
-
const DB_VERSION =
|
|
2
|
+
const DB_VERSION = 2; // Increment to invalidate old cache versions
|
|
3
3
|
const HERD_MODULES_STORE = "herd_modules";
|
|
4
4
|
const CACHE_METADATA_STORE = "cache_metadata";
|
|
5
5
|
// Default TTL: 24 hours (1 day)
|
|
@@ -22,39 +22,78 @@ export class ScoutCache {
|
|
|
22
22
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
23
23
|
request.onerror = () => {
|
|
24
24
|
console.error("[ScoutCache] Failed to open IndexedDB:", request.error);
|
|
25
|
+
this.db = null;
|
|
26
|
+
this.initPromise = null;
|
|
25
27
|
reject(request.error);
|
|
26
28
|
};
|
|
27
29
|
request.onsuccess = () => {
|
|
28
30
|
this.db = request.result;
|
|
29
31
|
console.log("[ScoutCache] IndexedDB initialized successfully");
|
|
32
|
+
// Add error handler for runtime database errors
|
|
33
|
+
this.db.onerror = (event) => {
|
|
34
|
+
console.error("[ScoutCache] Database error:", event);
|
|
35
|
+
};
|
|
30
36
|
resolve();
|
|
31
37
|
};
|
|
32
38
|
request.onupgradeneeded = (event) => {
|
|
33
39
|
const db = event.target.result;
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
try {
|
|
41
|
+
console.log(`[ScoutCache] Upgrading database to version ${DB_VERSION}`);
|
|
42
|
+
// Remove all existing object stores to ensure clean slate
|
|
43
|
+
const existingStores = Array.from(db.objectStoreNames);
|
|
44
|
+
for (const storeName of existingStores) {
|
|
45
|
+
console.log(`[ScoutCache] Removing existing object store: ${storeName}`);
|
|
46
|
+
db.deleteObjectStore(storeName);
|
|
47
|
+
}
|
|
48
|
+
// Create herd modules store (unified storage for all herd data)
|
|
36
49
|
const herdModulesStore = db.createObjectStore(HERD_MODULES_STORE, {
|
|
37
50
|
keyPath: "herdId",
|
|
38
51
|
});
|
|
39
52
|
herdModulesStore.createIndex("timestamp", "timestamp", {
|
|
40
53
|
unique: false,
|
|
41
54
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
herdModulesStore.createIndex("dbVersion", "dbVersion", {
|
|
56
|
+
unique: false,
|
|
57
|
+
});
|
|
58
|
+
console.log("[ScoutCache] Created herd_modules object store");
|
|
59
|
+
// Create cache metadata store
|
|
45
60
|
const metadataStore = db.createObjectStore(CACHE_METADATA_STORE, {
|
|
46
61
|
keyPath: "key",
|
|
47
62
|
});
|
|
63
|
+
console.log("[ScoutCache] Created cache_metadata object store");
|
|
64
|
+
console.log(`[ScoutCache] Database schema upgrade to version ${DB_VERSION} completed`);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error("[ScoutCache] Error during database upgrade:", error);
|
|
68
|
+
reject(error);
|
|
48
69
|
}
|
|
49
|
-
|
|
70
|
+
};
|
|
71
|
+
request.onblocked = () => {
|
|
72
|
+
console.warn("[ScoutCache] Database upgrade blocked - other connections may need to be closed");
|
|
50
73
|
};
|
|
51
74
|
});
|
|
52
75
|
return this.initPromise;
|
|
53
76
|
}
|
|
77
|
+
validateDatabaseSchema() {
|
|
78
|
+
if (!this.db)
|
|
79
|
+
return false;
|
|
80
|
+
const hasHerdModulesStore = this.db.objectStoreNames.contains(HERD_MODULES_STORE);
|
|
81
|
+
const hasMetadataStore = this.db.objectStoreNames.contains(CACHE_METADATA_STORE);
|
|
82
|
+
if (!hasHerdModulesStore) {
|
|
83
|
+
console.error("[ScoutCache] Missing herd_modules object store");
|
|
84
|
+
}
|
|
85
|
+
if (!hasMetadataStore) {
|
|
86
|
+
console.error("[ScoutCache] Missing cache_metadata object store");
|
|
87
|
+
}
|
|
88
|
+
return hasHerdModulesStore && hasMetadataStore;
|
|
89
|
+
}
|
|
54
90
|
async setHerdModules(herdModules, ttlMs = DEFAULT_TTL_MS, etag) {
|
|
55
91
|
await this.init();
|
|
56
92
|
if (!this.db)
|
|
57
93
|
throw new Error("Database not initialized");
|
|
94
|
+
if (!this.validateDatabaseSchema()) {
|
|
95
|
+
throw new Error("Database schema validation failed - required object stores not found");
|
|
96
|
+
}
|
|
58
97
|
const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
|
|
59
98
|
return new Promise((resolve, reject) => {
|
|
60
99
|
transaction.onerror = () => reject(transaction.error);
|
|
@@ -62,13 +101,14 @@ export class ScoutCache {
|
|
|
62
101
|
const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
|
|
63
102
|
const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
|
|
64
103
|
const timestamp = Date.now();
|
|
65
|
-
const version = "
|
|
66
|
-
// Store each herd module
|
|
104
|
+
const version = "2.0.0";
|
|
105
|
+
// Store each herd module (contains all nested data - devices, events, zones, etc.)
|
|
67
106
|
herdModules.forEach((herdModule) => {
|
|
68
107
|
const cacheEntry = {
|
|
69
108
|
herdId: herdModule.herd.id.toString(),
|
|
70
109
|
data: herdModule,
|
|
71
110
|
timestamp,
|
|
111
|
+
dbVersion: DB_VERSION,
|
|
72
112
|
};
|
|
73
113
|
herdModulesStore.put(cacheEntry);
|
|
74
114
|
});
|
|
@@ -78,6 +118,7 @@ export class ScoutCache {
|
|
|
78
118
|
timestamp,
|
|
79
119
|
ttl: ttlMs,
|
|
80
120
|
version,
|
|
121
|
+
dbVersion: DB_VERSION,
|
|
81
122
|
etag,
|
|
82
123
|
lastModified: timestamp,
|
|
83
124
|
};
|
|
@@ -88,6 +129,9 @@ export class ScoutCache {
|
|
|
88
129
|
await this.init();
|
|
89
130
|
if (!this.db)
|
|
90
131
|
throw new Error("Database not initialized");
|
|
132
|
+
if (!this.validateDatabaseSchema()) {
|
|
133
|
+
throw new Error("Database schema validation failed - required object stores not found");
|
|
134
|
+
}
|
|
91
135
|
const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readonly");
|
|
92
136
|
return new Promise((resolve, reject) => {
|
|
93
137
|
transaction.onerror = () => reject(transaction.error);
|
|
@@ -103,6 +147,17 @@ export class ScoutCache {
|
|
|
103
147
|
resolve({ data: null, isStale: true, age: 0, metadata: null });
|
|
104
148
|
return;
|
|
105
149
|
}
|
|
150
|
+
// Check if cache is from an incompatible DB version
|
|
151
|
+
if (!metadata.dbVersion || metadata.dbVersion !== DB_VERSION) {
|
|
152
|
+
console.log(`[ScoutCache] Cache from incompatible DB version (${metadata.dbVersion || "unknown"} !== ${DB_VERSION}), invalidating`);
|
|
153
|
+
this.stats.misses++;
|
|
154
|
+
// Clear old cache asynchronously
|
|
155
|
+
this.clearHerdModules().catch((error) => {
|
|
156
|
+
console.warn("[ScoutCache] Failed to clear old cache:", error);
|
|
157
|
+
});
|
|
158
|
+
resolve({ data: null, isStale: true, age: 0, metadata: null });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
106
161
|
const age = now - metadata.timestamp;
|
|
107
162
|
const isStale = age > metadata.ttl;
|
|
108
163
|
// Get all herd modules
|
|
@@ -110,7 +165,10 @@ export class ScoutCache {
|
|
|
110
165
|
getAllRequest.onsuccess = () => {
|
|
111
166
|
const cacheEntries = getAllRequest.result;
|
|
112
167
|
const herdModules = cacheEntries
|
|
113
|
-
.filter((entry) => entry.data &&
|
|
168
|
+
.filter((entry) => entry.data &&
|
|
169
|
+
entry.data.herd &&
|
|
170
|
+
entry.data.herd.slug &&
|
|
171
|
+
entry.dbVersion === DB_VERSION)
|
|
114
172
|
.map((entry) => entry.data)
|
|
115
173
|
.sort((a, b) => (a.herd?.slug || "").localeCompare(b.herd?.slug || ""));
|
|
116
174
|
// Update stats
|
|
@@ -134,6 +192,9 @@ export class ScoutCache {
|
|
|
134
192
|
await this.init();
|
|
135
193
|
if (!this.db)
|
|
136
194
|
throw new Error("Database not initialized");
|
|
195
|
+
if (!this.validateDatabaseSchema()) {
|
|
196
|
+
throw new Error("Database schema validation failed - required object stores not found");
|
|
197
|
+
}
|
|
137
198
|
const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
|
|
138
199
|
return new Promise((resolve, reject) => {
|
|
139
200
|
transaction.onerror = () => reject(transaction.error);
|
|
@@ -148,12 +209,16 @@ export class ScoutCache {
|
|
|
148
209
|
await this.init();
|
|
149
210
|
if (!this.db)
|
|
150
211
|
throw new Error("Database not initialized");
|
|
212
|
+
if (!this.validateDatabaseSchema()) {
|
|
213
|
+
throw new Error("Database schema validation failed - required object stores not found");
|
|
214
|
+
}
|
|
151
215
|
const transaction = this.db.transaction([CACHE_METADATA_STORE], "readwrite");
|
|
152
216
|
return new Promise((resolve, reject) => {
|
|
153
217
|
transaction.onerror = () => reject(transaction.error);
|
|
154
218
|
transaction.oncomplete = () => resolve();
|
|
155
219
|
const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
|
|
156
220
|
metadataStore.delete("herd_modules");
|
|
221
|
+
metadataStore.delete("providers");
|
|
157
222
|
});
|
|
158
223
|
}
|
|
159
224
|
async getCacheStats() {
|
|
@@ -180,7 +245,6 @@ export class ScoutCache {
|
|
|
180
245
|
const result = await this.getHerdModules();
|
|
181
246
|
return result.age;
|
|
182
247
|
}
|
|
183
|
-
// Method to check if we should refresh based on various conditions
|
|
184
248
|
async shouldRefresh(maxAgeMs, forceRefresh) {
|
|
185
249
|
if (forceRefresh) {
|
|
186
250
|
return { shouldRefresh: true, reason: "Force refresh requested" };
|
|
@@ -189,6 +253,15 @@ export class ScoutCache {
|
|
|
189
253
|
if (!result.data || result.data.length === 0) {
|
|
190
254
|
return { shouldRefresh: true, reason: "No cached data" };
|
|
191
255
|
}
|
|
256
|
+
// Check for DB version mismatch
|
|
257
|
+
if (!result.metadata ||
|
|
258
|
+
!result.metadata.dbVersion ||
|
|
259
|
+
result.metadata.dbVersion !== DB_VERSION) {
|
|
260
|
+
return {
|
|
261
|
+
shouldRefresh: true,
|
|
262
|
+
reason: `Cache from incompatible DB version (${result.metadata?.dbVersion || "unknown"} !== ${DB_VERSION})`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
192
265
|
if (result.isStale) {
|
|
193
266
|
return { shouldRefresh: true, reason: "Cache is stale" };
|
|
194
267
|
}
|
|
@@ -200,7 +273,6 @@ export class ScoutCache {
|
|
|
200
273
|
}
|
|
201
274
|
return { shouldRefresh: false, reason: "Cache is valid and fresh" };
|
|
202
275
|
}
|
|
203
|
-
// Method to preload cache with background refresh
|
|
204
276
|
async preloadCache(loadFunction, ttlMs = DEFAULT_TTL_MS) {
|
|
205
277
|
try {
|
|
206
278
|
console.log("[ScoutCache] Starting background cache preload...");
|
|
@@ -214,10 +286,85 @@ export class ScoutCache {
|
|
|
214
286
|
console.warn("[ScoutCache] Background preload failed:", error);
|
|
215
287
|
}
|
|
216
288
|
}
|
|
217
|
-
// Get the default TTL value
|
|
218
289
|
getDefaultTtl() {
|
|
219
290
|
return DEFAULT_TTL_MS;
|
|
220
291
|
}
|
|
292
|
+
getCurrentDbVersion() {
|
|
293
|
+
return DB_VERSION;
|
|
294
|
+
}
|
|
295
|
+
async isCacheVersionCompatible() {
|
|
296
|
+
try {
|
|
297
|
+
const result = await this.getHerdModules();
|
|
298
|
+
if (!result.metadata)
|
|
299
|
+
return false;
|
|
300
|
+
return (result.metadata.dbVersion !== undefined &&
|
|
301
|
+
result.metadata.dbVersion === DB_VERSION);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.warn("[ScoutCache] Version compatibility check failed:", error);
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async resetDatabase() {
|
|
309
|
+
console.log("[ScoutCache] Resetting database...");
|
|
310
|
+
// Close existing connection
|
|
311
|
+
if (this.db) {
|
|
312
|
+
this.db.close();
|
|
313
|
+
this.db = null;
|
|
314
|
+
}
|
|
315
|
+
this.initPromise = null;
|
|
316
|
+
// Delete the database
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
318
|
+
const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
|
|
319
|
+
deleteRequest.onsuccess = () => {
|
|
320
|
+
console.log("[ScoutCache] Database reset successfully");
|
|
321
|
+
resolve();
|
|
322
|
+
};
|
|
323
|
+
deleteRequest.onerror = () => {
|
|
324
|
+
console.error("[ScoutCache] Failed to reset database:", deleteRequest.error);
|
|
325
|
+
reject(deleteRequest.error);
|
|
326
|
+
};
|
|
327
|
+
deleteRequest.onblocked = () => {
|
|
328
|
+
console.warn("[ScoutCache] Database reset blocked - close all other tabs");
|
|
329
|
+
// Continue anyway, it will resolve when unblocked
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async checkDatabaseHealth() {
|
|
334
|
+
const issues = [];
|
|
335
|
+
try {
|
|
336
|
+
await this.init();
|
|
337
|
+
if (!this.db) {
|
|
338
|
+
issues.push("Database connection not established");
|
|
339
|
+
return { healthy: false, issues };
|
|
340
|
+
}
|
|
341
|
+
if (!this.validateDatabaseSchema()) {
|
|
342
|
+
issues.push("Database schema validation failed");
|
|
343
|
+
}
|
|
344
|
+
// Check version compatibility
|
|
345
|
+
const isVersionCompatible = await this.isCacheVersionCompatible();
|
|
346
|
+
if (!isVersionCompatible) {
|
|
347
|
+
issues.push(`Cache version incompatible (current: ${DB_VERSION})`);
|
|
348
|
+
}
|
|
349
|
+
// Try a simple read operation
|
|
350
|
+
try {
|
|
351
|
+
const result = await this.getHerdModules();
|
|
352
|
+
if (result.data === null && result.age === 0) {
|
|
353
|
+
// This is expected for empty cache, not an error
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
issues.push(`Read operation failed: ${error}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
issues.push(`Database initialization failed: ${error}`);
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
healthy: issues.length === 0,
|
|
365
|
+
issues,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
221
368
|
}
|
|
222
369
|
// Singleton instance
|
|
223
370
|
export const scoutCache = new ScoutCache();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CacheStats, TimingStats } from "../helpers/cache";
|
|
1
|
+
import { CacheStats, TimingStats, DatabaseHealth } from "../helpers/cache";
|
|
2
2
|
export interface UseScoutRefreshOptions {
|
|
3
3
|
autoRefresh?: boolean;
|
|
4
4
|
onRefreshComplete?: () => void;
|
|
@@ -41,4 +41,8 @@ export declare function useScoutRefresh(options?: UseScoutRefreshOptions): {
|
|
|
41
41
|
getTimingStats: () => TimingStats;
|
|
42
42
|
clearCache: () => Promise<void>;
|
|
43
43
|
getCacheStats: () => Promise<CacheStats>;
|
|
44
|
+
checkDatabaseHealth: () => Promise<DatabaseHealth>;
|
|
45
|
+
resetDatabase: () => Promise<void>;
|
|
46
|
+
isCacheVersionCompatible: () => Promise<boolean>;
|
|
47
|
+
getCurrentDbVersion: () => number;
|
|
44
48
|
};
|
|
@@ -4,7 +4,7 @@ import { EnumScoutStateStatus, setActiveHerdId, setHerdModules, setStatus, setHe
|
|
|
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 { scoutCache } from "../helpers/cache";
|
|
7
|
+
import { scoutCache, } from "../helpers/cache";
|
|
8
8
|
import { EnumDataSource } from "../types/data_source";
|
|
9
9
|
/**
|
|
10
10
|
* Hook for refreshing scout data with detailed timing measurements and cache-first loading
|
|
@@ -107,7 +107,7 @@ export function useScoutRefresh(options = {}) {
|
|
|
107
107
|
const backgroundStartTime = Date.now();
|
|
108
108
|
const [backgroundHerdModulesResult, backgroundUserResult] = await Promise.all([
|
|
109
109
|
server_load_herd_modules(),
|
|
110
|
-
server_get_user()
|
|
110
|
+
server_get_user(),
|
|
111
111
|
]);
|
|
112
112
|
const backgroundDuration = Date.now() - backgroundStartTime;
|
|
113
113
|
console.log(`[useScoutRefresh] Background fetch completed in ${backgroundDuration}ms`);
|
|
@@ -123,6 +123,21 @@ export function useScoutRefresh(options = {}) {
|
|
|
123
123
|
}
|
|
124
124
|
catch (cacheError) {
|
|
125
125
|
console.warn("[useScoutRefresh] Background cache save failed:", cacheError);
|
|
126
|
+
// If it's an IndexedDB object store error, try to reset the database
|
|
127
|
+
if (cacheError instanceof Error &&
|
|
128
|
+
(cacheError.message.includes("object store") ||
|
|
129
|
+
cacheError.message.includes("NotFoundError"))) {
|
|
130
|
+
console.log("[useScoutRefresh] Attempting database reset due to schema error...");
|
|
131
|
+
try {
|
|
132
|
+
await scoutCache.resetDatabase();
|
|
133
|
+
console.log("[useScoutRefresh] Database reset successful, retrying cache save...");
|
|
134
|
+
await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs);
|
|
135
|
+
console.log("[useScoutRefresh] Cache save successful after database reset");
|
|
136
|
+
}
|
|
137
|
+
catch (resetError) {
|
|
138
|
+
console.error("[useScoutRefresh] Database reset and retry failed:", resetError);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
126
141
|
}
|
|
127
142
|
// Update store with fresh data
|
|
128
143
|
dispatch(setHerdModules(backgroundHerdModulesResult.data));
|
|
@@ -157,6 +172,19 @@ export function useScoutRefresh(options = {}) {
|
|
|
157
172
|
}
|
|
158
173
|
catch (cacheError) {
|
|
159
174
|
console.warn("[useScoutRefresh] Cache load failed:", cacheError);
|
|
175
|
+
// If it's an IndexedDB object store error, try to reset the database
|
|
176
|
+
if (cacheError instanceof Error &&
|
|
177
|
+
(cacheError.message.includes("object store") ||
|
|
178
|
+
cacheError.message.includes("NotFoundError"))) {
|
|
179
|
+
console.log("[useScoutRefresh] Attempting database reset due to cache load error...");
|
|
180
|
+
try {
|
|
181
|
+
await scoutCache.resetDatabase();
|
|
182
|
+
console.log("[useScoutRefresh] Database reset successful");
|
|
183
|
+
}
|
|
184
|
+
catch (resetError) {
|
|
185
|
+
console.error("[useScoutRefresh] Database reset failed:", resetError);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
160
188
|
// Continue with API call
|
|
161
189
|
}
|
|
162
190
|
}
|
|
@@ -223,6 +251,21 @@ export function useScoutRefresh(options = {}) {
|
|
|
223
251
|
}
|
|
224
252
|
catch (cacheError) {
|
|
225
253
|
console.warn("[useScoutRefresh] Cache save failed:", cacheError);
|
|
254
|
+
// If it's an IndexedDB object store error, try to reset the database
|
|
255
|
+
if (cacheError instanceof Error &&
|
|
256
|
+
(cacheError.message.includes("object store") ||
|
|
257
|
+
cacheError.message.includes("NotFoundError"))) {
|
|
258
|
+
console.log("[useScoutRefresh] Attempting database reset due to cache save error...");
|
|
259
|
+
try {
|
|
260
|
+
await scoutCache.resetDatabase();
|
|
261
|
+
console.log("[useScoutRefresh] Database reset successful, retrying cache save...");
|
|
262
|
+
await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs);
|
|
263
|
+
console.log("[useScoutRefresh] Cache save successful after database reset");
|
|
264
|
+
}
|
|
265
|
+
catch (resetError) {
|
|
266
|
+
console.error("[useScoutRefresh] Database reset and retry failed:", resetError);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
226
269
|
}
|
|
227
270
|
// Step 4: Update store with fresh data
|
|
228
271
|
const dataProcessingStartTime = Date.now();
|
|
@@ -345,10 +388,52 @@ export function useScoutRefresh(options = {}) {
|
|
|
345
388
|
};
|
|
346
389
|
}
|
|
347
390
|
}, []);
|
|
391
|
+
// Utility function to check database health
|
|
392
|
+
const checkDatabaseHealth = useCallback(async () => {
|
|
393
|
+
try {
|
|
394
|
+
return await scoutCache.checkDatabaseHealth();
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error("[useScoutRefresh] Failed to check database health:", error);
|
|
398
|
+
return {
|
|
399
|
+
healthy: false,
|
|
400
|
+
issues: [`Health check failed: ${error}`],
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}, []);
|
|
404
|
+
// Utility function to reset database
|
|
405
|
+
const resetDatabase = useCallback(async () => {
|
|
406
|
+
try {
|
|
407
|
+
await scoutCache.resetDatabase();
|
|
408
|
+
console.log("[useScoutRefresh] Database reset successfully");
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
console.error("[useScoutRefresh] Failed to reset database:", error);
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}, []);
|
|
415
|
+
// Utility function to check cache version compatibility
|
|
416
|
+
const isCacheVersionCompatible = useCallback(async () => {
|
|
417
|
+
try {
|
|
418
|
+
return await scoutCache.isCacheVersionCompatible();
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.error("[useScoutRefresh] Failed to check cache version compatibility:", error);
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}, []);
|
|
425
|
+
// Utility function to get current DB version
|
|
426
|
+
const getCurrentDbVersion = useCallback(() => {
|
|
427
|
+
return scoutCache.getCurrentDbVersion();
|
|
428
|
+
}, []);
|
|
348
429
|
return {
|
|
349
430
|
handleRefresh,
|
|
350
431
|
getTimingStats,
|
|
351
432
|
clearCache,
|
|
352
433
|
getCacheStats,
|
|
434
|
+
checkDatabaseHealth,
|
|
435
|
+
resetDatabase,
|
|
436
|
+
isCacheVersionCompatible,
|
|
437
|
+
getCurrentDbVersion,
|
|
353
438
|
};
|
|
354
439
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from "./helpers/web";
|
|
|
30
30
|
export * from "./helpers/zones";
|
|
31
31
|
export * from "./helpers/storage";
|
|
32
32
|
export * from "./helpers/eventUtils";
|
|
33
|
+
export * from "./helpers/cache";
|
|
33
34
|
export * from "./hooks/useScoutDbListener";
|
|
34
35
|
export * from "./hooks/useScoutRefresh";
|
|
35
36
|
export * from "./providers";
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export * from "./helpers/web";
|
|
|
33
33
|
export * from "./helpers/zones";
|
|
34
34
|
export * from "./helpers/storage";
|
|
35
35
|
export * from "./helpers/eventUtils";
|
|
36
|
+
export * from "./helpers/cache";
|
|
36
37
|
// Hooks
|
|
37
38
|
export * from "./hooks/useScoutDbListener";
|
|
38
39
|
export * from "./hooks/useScoutRefresh";
|
package/dist/types/db.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type IHerd = Database["public"]["Tables"]["herds"]["Row"];
|
|
|
22
22
|
export type ISession = Database["public"]["Tables"]["sessions"]["Row"];
|
|
23
23
|
export type IConnectivity = Database["public"]["Tables"]["connectivity"]["Row"];
|
|
24
24
|
export type IHeartbeat = Database["public"]["Tables"]["heartbeats"]["Row"];
|
|
25
|
+
export type IProvider = Database["public"]["Tables"]["providers"]["Row"];
|
|
25
26
|
export type IEventWithTags = Database["public"]["CompositeTypes"]["event_with_tags"] & {
|
|
26
27
|
earthranger_url: string | null;
|
|
27
28
|
file_path: string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
|
|
2
|
+
import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IProvider, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
|
|
3
3
|
import { EnumWebResponse } from "./requests";
|
|
4
4
|
export declare enum EnumHerdModulesLoadingState {
|
|
5
5
|
NOT_LOADING = "NOT_LOADING",
|
|
@@ -21,7 +21,8 @@ export declare class HerdModule {
|
|
|
21
21
|
labels: string[];
|
|
22
22
|
plans: IPlan[];
|
|
23
23
|
layers: ILayer[];
|
|
24
|
-
|
|
24
|
+
providers: IProvider[];
|
|
25
|
+
constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[], layers?: ILayer[], providers?: IProvider[]);
|
|
25
26
|
to_serializable(): IHerdModule;
|
|
26
27
|
static from_herd(herd: IHerd, client: SupabaseClient): Promise<HerdModule>;
|
|
27
28
|
}
|
|
@@ -39,6 +40,7 @@ export interface IHerdModule {
|
|
|
39
40
|
zones: IZoneWithActions[];
|
|
40
41
|
sessions: ISessionWithCoordinates[];
|
|
41
42
|
layers: ILayer[];
|
|
43
|
+
providers: IProvider[];
|
|
42
44
|
}
|
|
43
45
|
export interface IHerdModulesResponse {
|
|
44
46
|
data: IHerdModule[];
|
|
@@ -4,6 +4,7 @@ import { server_get_total_events_by_herd } from "../helpers/events";
|
|
|
4
4
|
import { EnumSessionsVisibility } from "./events";
|
|
5
5
|
import { server_get_plans_by_herd } from "../helpers/plans";
|
|
6
6
|
import { server_get_layers_by_herd } from "../helpers/layers";
|
|
7
|
+
import { server_get_providers_by_herd } from "../helpers/providers";
|
|
7
8
|
import { server_get_events_and_tags_for_devices_batch } from "../helpers/tags";
|
|
8
9
|
import { server_get_users_with_herd_access } from "../helpers/users";
|
|
9
10
|
import { EnumWebResponse } from "./requests";
|
|
@@ -18,7 +19,7 @@ export var EnumHerdModulesLoadingState;
|
|
|
18
19
|
EnumHerdModulesLoadingState["UNSUCCESSFULLY_LOADED"] = "UNSUCCESSFULLY_LOADED";
|
|
19
20
|
})(EnumHerdModulesLoadingState || (EnumHerdModulesLoadingState = {}));
|
|
20
21
|
export class HerdModule {
|
|
21
|
-
constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = []) {
|
|
22
|
+
constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = [], providers = []) {
|
|
22
23
|
this.user_roles = null;
|
|
23
24
|
this.events_page_index = 0;
|
|
24
25
|
this.total_events = 0;
|
|
@@ -26,6 +27,7 @@ export class HerdModule {
|
|
|
26
27
|
this.labels = [];
|
|
27
28
|
this.plans = [];
|
|
28
29
|
this.layers = [];
|
|
30
|
+
this.providers = [];
|
|
29
31
|
this.herd = herd;
|
|
30
32
|
this.devices = devices;
|
|
31
33
|
this.events = events;
|
|
@@ -39,6 +41,7 @@ export class HerdModule {
|
|
|
39
41
|
this.zones = zones;
|
|
40
42
|
this.sessions = sessions;
|
|
41
43
|
this.layers = layers;
|
|
44
|
+
this.providers = providers;
|
|
42
45
|
}
|
|
43
46
|
to_serializable() {
|
|
44
47
|
return {
|
|
@@ -55,6 +58,7 @@ export class HerdModule {
|
|
|
55
58
|
zones: this.zones,
|
|
56
59
|
sessions: this.sessions,
|
|
57
60
|
layers: this.layers,
|
|
61
|
+
providers: this.providers,
|
|
58
62
|
};
|
|
59
63
|
}
|
|
60
64
|
static async from_herd(herd, client) {
|
|
@@ -95,7 +99,7 @@ export class HerdModule {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
// Run all remaining requests in parallel with individual error handling
|
|
98
|
-
const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers,] = await Promise.allSettled([
|
|
102
|
+
const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers, res_providers,] = await Promise.allSettled([
|
|
99
103
|
server_get_more_zones_and_actions_for_herd(herd.id, 0, 10).catch((error) => {
|
|
100
104
|
console.warn(`[HerdModule] Failed to get zones and actions:`, error);
|
|
101
105
|
return { status: EnumWebResponse.ERROR, data: null };
|
|
@@ -114,12 +118,20 @@ export class HerdModule {
|
|
|
114
118
|
}),
|
|
115
119
|
server_get_sessions_by_herd_id(herd.id).catch((error) => {
|
|
116
120
|
console.warn(`[HerdModule] Failed to get sessions:`, error);
|
|
117
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
status: EnumWebResponse.ERROR,
|
|
123
|
+
data: [],
|
|
124
|
+
msg: error.message,
|
|
125
|
+
};
|
|
118
126
|
}),
|
|
119
127
|
server_get_layers_by_herd(herd.id).catch((error) => {
|
|
120
128
|
console.warn(`[HerdModule] Failed to get layers:`, error);
|
|
121
129
|
return { status: EnumWebResponse.ERROR, data: null };
|
|
122
130
|
}),
|
|
131
|
+
server_get_providers_by_herd(herd.id).catch((error) => {
|
|
132
|
+
console.warn(`[HerdModule] Failed to get providers:`, error);
|
|
133
|
+
return { status: EnumWebResponse.ERROR, data: null };
|
|
134
|
+
}),
|
|
123
135
|
]);
|
|
124
136
|
// Assign recent events to devices from batch results
|
|
125
137
|
for (let i = 0; i < new_devices.length; i++) {
|
|
@@ -148,23 +160,28 @@ export class HerdModule {
|
|
|
148
160
|
const plans = res_plans.status === "fulfilled" && res_plans.value?.data
|
|
149
161
|
? res_plans.value.data
|
|
150
162
|
: [];
|
|
151
|
-
const sessions = res_sessions.status === "fulfilled" && res_sessions.value?.data
|
|
163
|
+
const sessions = res_sessions.status === "fulfilled" && res_sessions.value?.data
|
|
164
|
+
? res_sessions.value.data
|
|
165
|
+
: [];
|
|
152
166
|
const layers = res_layers.status === "fulfilled" && res_layers.value?.data
|
|
153
167
|
? res_layers.value.data
|
|
154
168
|
: [];
|
|
169
|
+
const providers = res_providers.status === "fulfilled" && res_providers.value?.data
|
|
170
|
+
? res_providers.value.data
|
|
171
|
+
: [];
|
|
155
172
|
// TODO: store in DB and retrieve on load?
|
|
156
173
|
const newLabels = LABELS;
|
|
157
174
|
const endTime = Date.now();
|
|
158
175
|
const loadTime = endTime - startTime;
|
|
159
176
|
console.log(`[HerdModule] Loaded herd ${herd.slug} in ${loadTime}ms (${new_devices.length} devices)`);
|
|
160
|
-
return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers);
|
|
177
|
+
return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers, providers);
|
|
161
178
|
}
|
|
162
179
|
catch (error) {
|
|
163
180
|
const endTime = Date.now();
|
|
164
181
|
const loadTime = endTime - startTime;
|
|
165
182
|
console.error(`[HerdModule] Critical error in HerdModule.from_herd (${loadTime}ms):`, error);
|
|
166
183
|
// Return a minimal but valid HerdModule instance to prevent complete failure
|
|
167
|
-
return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], []);
|
|
184
|
+
return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], [], []);
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
}
|