@adventurelabs/scout-core 1.0.84 → 1.0.85

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.
@@ -30,11 +30,16 @@ export interface TimingStats {
30
30
  dataProcessing: number;
31
31
  localStorage: number;
32
32
  }
33
+ export interface DatabaseHealth {
34
+ healthy: boolean;
35
+ issues: string[];
36
+ }
33
37
  export declare class ScoutCache {
34
38
  private db;
35
39
  private initPromise;
36
40
  private stats;
37
41
  private init;
42
+ private validateDatabaseSchema;
38
43
  setHerdModules(herdModules: IHerdModule[], ttlMs?: number, etag?: string): Promise<void>;
39
44
  getHerdModules(): Promise<CacheResult<IHerdModule[]>>;
40
45
  clearHerdModules(): Promise<void>;
@@ -48,5 +53,7 @@ export declare class ScoutCache {
48
53
  }>;
49
54
  preloadCache(loadFunction: () => Promise<IHerdModule[]>, ttlMs?: number): Promise<void>;
50
55
  getDefaultTtl(): number;
56
+ resetDatabase(): Promise<void>;
57
+ checkDatabaseHealth(): Promise<DatabaseHealth>;
51
58
  }
52
59
  export declare const scoutCache: ScoutCache;
@@ -22,39 +22,75 @@ 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;
31
+ // Validate that required object stores exist
32
+ if (!this.validateDatabaseSchema()) {
33
+ console.error("[ScoutCache] Database schema validation failed");
34
+ this.db.close();
35
+ this.db = null;
36
+ this.initPromise = null;
37
+ reject(new Error("Database schema validation failed"));
38
+ return;
39
+ }
29
40
  console.log("[ScoutCache] IndexedDB initialized successfully");
30
41
  resolve();
31
42
  };
32
43
  request.onupgradeneeded = (event) => {
33
44
  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, {
37
- keyPath: "herdId",
38
- });
39
- herdModulesStore.createIndex("timestamp", "timestamp", {
40
- unique: false,
41
- });
45
+ try {
46
+ // Create herd modules store
47
+ if (!db.objectStoreNames.contains(HERD_MODULES_STORE)) {
48
+ const herdModulesStore = db.createObjectStore(HERD_MODULES_STORE, {
49
+ keyPath: "herdId",
50
+ });
51
+ herdModulesStore.createIndex("timestamp", "timestamp", {
52
+ unique: false,
53
+ });
54
+ console.log("[ScoutCache] Created herd_modules object store");
55
+ }
56
+ // Create cache metadata store
57
+ if (!db.objectStoreNames.contains(CACHE_METADATA_STORE)) {
58
+ const metadataStore = db.createObjectStore(CACHE_METADATA_STORE, {
59
+ keyPath: "key",
60
+ });
61
+ console.log("[ScoutCache] Created cache_metadata object store");
62
+ }
63
+ console.log("[ScoutCache] Database schema upgraded");
42
64
  }
43
- // Create cache metadata store
44
- if (!db.objectStoreNames.contains(CACHE_METADATA_STORE)) {
45
- const metadataStore = db.createObjectStore(CACHE_METADATA_STORE, {
46
- keyPath: "key",
47
- });
65
+ catch (error) {
66
+ console.error("[ScoutCache] Error during database upgrade:", error);
67
+ reject(error);
48
68
  }
49
- console.log("[ScoutCache] Database schema upgraded");
50
69
  };
51
70
  });
52
71
  return this.initPromise;
53
72
  }
73
+ validateDatabaseSchema() {
74
+ if (!this.db)
75
+ return false;
76
+ const hasHerdModulesStore = this.db.objectStoreNames.contains(HERD_MODULES_STORE);
77
+ const hasMetadataStore = this.db.objectStoreNames.contains(CACHE_METADATA_STORE);
78
+ if (!hasHerdModulesStore) {
79
+ console.error("[ScoutCache] Missing herd_modules object store");
80
+ }
81
+ if (!hasMetadataStore) {
82
+ console.error("[ScoutCache] Missing cache_metadata object store");
83
+ }
84
+ return hasHerdModulesStore && hasMetadataStore;
85
+ }
54
86
  async setHerdModules(herdModules, ttlMs = DEFAULT_TTL_MS, etag) {
55
87
  await this.init();
56
88
  if (!this.db)
57
89
  throw new Error("Database not initialized");
90
+ // Validate schema before creating transaction
91
+ if (!this.validateDatabaseSchema()) {
92
+ throw new Error("Database schema validation failed - required object stores not found");
93
+ }
58
94
  const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
59
95
  return new Promise((resolve, reject) => {
60
96
  transaction.onerror = () => reject(transaction.error);
@@ -88,6 +124,10 @@ export class ScoutCache {
88
124
  await this.init();
89
125
  if (!this.db)
90
126
  throw new Error("Database not initialized");
127
+ // Validate schema before creating transaction
128
+ if (!this.validateDatabaseSchema()) {
129
+ throw new Error("Database schema validation failed - required object stores not found");
130
+ }
91
131
  const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readonly");
92
132
  return new Promise((resolve, reject) => {
93
133
  transaction.onerror = () => reject(transaction.error);
@@ -134,6 +174,10 @@ export class ScoutCache {
134
174
  await this.init();
135
175
  if (!this.db)
136
176
  throw new Error("Database not initialized");
177
+ // Validate schema before creating transaction
178
+ if (!this.validateDatabaseSchema()) {
179
+ throw new Error("Database schema validation failed - required object stores not found");
180
+ }
137
181
  const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
138
182
  return new Promise((resolve, reject) => {
139
183
  transaction.onerror = () => reject(transaction.error);
@@ -148,6 +192,10 @@ export class ScoutCache {
148
192
  await this.init();
149
193
  if (!this.db)
150
194
  throw new Error("Database not initialized");
195
+ // Validate schema before creating transaction
196
+ if (!this.validateDatabaseSchema()) {
197
+ throw new Error("Database schema validation failed - required object stores not found");
198
+ }
151
199
  const transaction = this.db.transaction([CACHE_METADATA_STORE], "readwrite");
152
200
  return new Promise((resolve, reject) => {
153
201
  transaction.onerror = () => reject(transaction.error);
@@ -218,6 +266,60 @@ export class ScoutCache {
218
266
  getDefaultTtl() {
219
267
  return DEFAULT_TTL_MS;
220
268
  }
269
+ // Method to reset the database in case of corruption
270
+ async resetDatabase() {
271
+ console.log("[ScoutCache] Resetting database...");
272
+ // Close existing connection
273
+ if (this.db) {
274
+ this.db.close();
275
+ this.db = null;
276
+ }
277
+ this.initPromise = null;
278
+ // Delete the database
279
+ return new Promise((resolve, reject) => {
280
+ const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
281
+ deleteRequest.onsuccess = () => {
282
+ console.log("[ScoutCache] Database reset successfully");
283
+ resolve();
284
+ };
285
+ deleteRequest.onerror = () => {
286
+ console.error("[ScoutCache] Failed to reset database:", deleteRequest.error);
287
+ reject(deleteRequest.error);
288
+ };
289
+ deleteRequest.onblocked = () => {
290
+ console.warn("[ScoutCache] Database reset blocked - close all other tabs");
291
+ // Continue anyway, it will resolve when unblocked
292
+ };
293
+ });
294
+ }
295
+ // Method to check database health
296
+ async checkDatabaseHealth() {
297
+ const issues = [];
298
+ try {
299
+ await this.init();
300
+ if (!this.db) {
301
+ issues.push("Database connection not established");
302
+ return { healthy: false, issues };
303
+ }
304
+ if (!this.validateDatabaseSchema()) {
305
+ issues.push("Database schema validation failed");
306
+ }
307
+ // Try a simple read operation
308
+ try {
309
+ await this.getHerdModules();
310
+ }
311
+ catch (error) {
312
+ issues.push(`Read operation failed: ${error}`);
313
+ }
314
+ }
315
+ catch (error) {
316
+ issues.push(`Database initialization failed: ${error}`);
317
+ }
318
+ return {
319
+ healthy: issues.length === 0,
320
+ issues,
321
+ };
322
+ }
221
323
  }
222
324
  // Singleton instance
223
325
  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,6 @@ 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>;
44
46
  };
@@ -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,36 @@ 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
+ }, []);
348
415
  return {
349
416
  handleRefresh,
350
417
  getTimingStats,
351
418
  clearCache,
352
419
  getCacheStats,
420
+ checkDatabaseHealth,
421
+ resetDatabase,
353
422
  };
354
423
  }
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.84",
3
+ "version": "1.0.85",
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",