@adventurelabs/scout-core 2.0.0 → 2.0.2

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.
@@ -1,52 +1,114 @@
1
- const DB_NAME = "ScoutCache";
2
- const DB_VERSION = 8;
1
+ const LEGACY_DB_NAME = "ScoutCache";
2
+ const DB_VERSION = 9;
3
3
  const HERD_MODULES_STORE = "herd_modules";
4
4
  const JWT_MINTS_STORE = "jwt_mints";
5
5
  const CACHE_METADATA_STORE = "cache_metadata";
6
- // Default TTL: 24 hours (1 day)
7
6
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
7
+ let legacyDbDeleteStarted = false;
8
+ function getSupabaseProjectRef() {
9
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
10
+ if (!url) {
11
+ throw new Error("Missing NEXT_PUBLIC_SUPABASE_URL");
12
+ }
13
+ const ref = new URL(url).hostname.split(".")[0];
14
+ if (!ref) {
15
+ throw new Error("Supabase URL has no project ref");
16
+ }
17
+ return ref;
18
+ }
19
+ function buildDbName(userId) {
20
+ return `ScoutCache_${getSupabaseProjectRef()}_${userId}`;
21
+ }
22
+ function deleteLegacyDatabase() {
23
+ if (legacyDbDeleteStarted || typeof indexedDB === "undefined")
24
+ return;
25
+ legacyDbDeleteStarted = true;
26
+ indexedDB.deleteDatabase(LEGACY_DB_NAME);
27
+ }
28
+ function transactionComplete(transaction) {
29
+ return new Promise((resolve, reject) => {
30
+ transaction.oncomplete = () => resolve();
31
+ transaction.onerror = () => reject(transaction.error);
32
+ transaction.onabort = () => reject(transaction.error);
33
+ });
34
+ }
8
35
  export class ScoutCache {
9
36
  constructor() {
10
- this.db = null;
11
- this.initPromise = null;
37
+ this.dbName = null;
38
+ this.userId = null;
39
+ this.databases = new Map();
40
+ this.initPromises = new Map();
41
+ this.deletePromises = new Map();
12
42
  this.stats = {
13
43
  hits: 0,
14
44
  misses: 0,
15
45
  };
16
46
  }
17
- async init() {
18
- if (this.db)
19
- return;
20
- if (this.initPromise)
21
- return this.initPromise;
22
- this.initPromise = new Promise((resolve, reject) => {
23
- const request = indexedDB.open(DB_NAME, DB_VERSION);
47
+ async setScope(userId) {
48
+ if (!userId) {
49
+ throw new Error("ScoutCache.setScope requires a user id");
50
+ }
51
+ const nextName = buildDbName(userId);
52
+ if (this.dbName !== nextName) {
53
+ this.stats = { hits: 0, misses: 0 };
54
+ }
55
+ this.dbName = nextName;
56
+ this.userId = userId;
57
+ deleteLegacyDatabase();
58
+ await this.getDatabase(userId);
59
+ }
60
+ getScopeUserId() {
61
+ return this.userId;
62
+ }
63
+ async getDatabase(userId) {
64
+ const dbName = userId ? buildDbName(userId) : this.dbName;
65
+ if (!dbName) {
66
+ throw new Error("ScoutCache scope not set — call setScope(userId) first");
67
+ }
68
+ const db = await this.init(dbName);
69
+ if (!this.validateDatabaseSchema(db)) {
70
+ throw new Error("ScoutCache database schema is invalid");
71
+ }
72
+ return db;
73
+ }
74
+ async init(dbName) {
75
+ const deleting = this.deletePromises.get(dbName);
76
+ if (deleting) {
77
+ await deleting;
78
+ }
79
+ const database = this.databases.get(dbName);
80
+ if (database)
81
+ return database;
82
+ const pending = this.initPromises.get(dbName);
83
+ if (pending)
84
+ return pending;
85
+ const initPromise = new Promise((resolve, reject) => {
86
+ const request = indexedDB.open(dbName, DB_VERSION);
24
87
  request.onerror = () => {
25
88
  console.error("[ScoutCache] Failed to open IndexedDB:", request.error);
26
- this.db = null;
27
- this.initPromise = null;
28
89
  reject(request.error);
29
90
  };
30
91
  request.onsuccess = () => {
31
- this.db = request.result;
32
- console.log("[ScoutCache] IndexedDB initialized successfully");
33
- // Add error handler for runtime database errors
34
- this.db.onerror = (event) => {
92
+ const openedDatabase = request.result;
93
+ this.databases.set(dbName, openedDatabase);
94
+ openedDatabase.onerror = (event) => {
35
95
  console.error("[ScoutCache] Database error:", event);
36
96
  };
37
- resolve();
97
+ openedDatabase.onversionchange = () => {
98
+ openedDatabase.close();
99
+ if (this.databases.get(dbName) === openedDatabase) {
100
+ this.databases.delete(dbName);
101
+ }
102
+ };
103
+ resolve(openedDatabase);
38
104
  };
39
105
  request.onupgradeneeded = (event) => {
40
106
  const db = event.target.result;
41
107
  try {
42
- console.log(`[ScoutCache] Upgrading database to version ${DB_VERSION}`);
43
- // Remove all existing object stores to ensure clean slate
44
108
  const existingStores = Array.from(db.objectStoreNames);
45
109
  for (const storeName of existingStores) {
46
- console.log(`[ScoutCache] Removing existing object store: ${storeName}`);
47
110
  db.deleteObjectStore(storeName);
48
111
  }
49
- // Create herd modules store (unified storage for all herd data)
50
112
  const herdModulesStore = db.createObjectStore(HERD_MODULES_STORE, {
51
113
  keyPath: "herdId",
52
114
  });
@@ -56,20 +118,15 @@ export class ScoutCache {
56
118
  herdModulesStore.createIndex("dbVersion", "dbVersion", {
57
119
  unique: false,
58
120
  });
59
- console.log("[ScoutCache] Created herd_modules object store");
60
121
  const jwtMintsStore = db.createObjectStore(JWT_MINTS_STORE, {
61
122
  keyPath: "key",
62
123
  });
63
124
  jwtMintsStore.createIndex("timestamp", "timestamp", {
64
125
  unique: false,
65
126
  });
66
- console.log("[ScoutCache] Created jwt_mints object store");
67
- // Create cache metadata store
68
- const metadataStore = db.createObjectStore(CACHE_METADATA_STORE, {
127
+ db.createObjectStore(CACHE_METADATA_STORE, {
69
128
  keyPath: "key",
70
129
  });
71
- console.log("[ScoutCache] Created cache_metadata object store");
72
- console.log(`[ScoutCache] Database schema upgrade to version ${DB_VERSION} completed`);
73
130
  }
74
131
  catch (error) {
75
132
  console.error("[ScoutCache] Error during database upgrade:", error);
@@ -80,78 +137,59 @@ export class ScoutCache {
80
137
  console.warn("[ScoutCache] Database upgrade blocked - other connections may need to be closed");
81
138
  };
82
139
  });
83
- return this.initPromise;
84
- }
85
- validateDatabaseSchema() {
86
- if (!this.db)
87
- return false;
88
- const hasHerdModulesStore = this.db.objectStoreNames.contains(HERD_MODULES_STORE);
89
- const hasJwtMintsStore = this.db.objectStoreNames.contains(JWT_MINTS_STORE);
90
- const hasMetadataStore = this.db.objectStoreNames.contains(CACHE_METADATA_STORE);
91
- if (!hasHerdModulesStore) {
92
- console.error("[ScoutCache] Missing herd_modules object store");
93
- }
94
- if (!hasJwtMintsStore) {
95
- console.error("[ScoutCache] Missing jwt_mints object store");
140
+ this.initPromises.set(dbName, initPromise);
141
+ try {
142
+ return await initPromise;
96
143
  }
97
- if (!hasMetadataStore) {
98
- console.error("[ScoutCache] Missing cache_metadata object store");
144
+ finally {
145
+ if (this.initPromises.get(dbName) === initPromise) {
146
+ this.initPromises.delete(dbName);
147
+ }
99
148
  }
100
- return hasHerdModulesStore && hasJwtMintsStore && hasMetadataStore;
101
149
  }
102
- async setHerdModules(herdModules, ttlMs = DEFAULT_TTL_MS, etag) {
103
- await this.init();
104
- if (!this.db)
105
- throw new Error("Database not initialized");
106
- if (!this.validateDatabaseSchema()) {
107
- throw new Error("Database schema validation failed - required object stores not found");
108
- }
109
- const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
110
- return new Promise((resolve, reject) => {
111
- transaction.onerror = () => reject(transaction.error);
112
- transaction.oncomplete = () => resolve();
113
- const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
114
- const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
115
- const timestamp = Date.now();
116
- const version = "2.0.0";
117
- // Store each herd module (contains all nested data - devices, events, zones, etc.)
118
- herdModules.forEach((herdModule) => {
119
- if (herdModule.herd.id == null)
120
- return;
121
- const cacheEntry = {
122
- herdId: herdModule.herd.id.toString(),
123
- data: herdModule,
124
- timestamp,
125
- dbVersion: DB_VERSION,
126
- };
127
- herdModulesStore.put(cacheEntry);
128
- });
129
- // Store cache metadata
130
- const metadata = {
131
- key: "herd_modules",
150
+ validateDatabaseSchema(db) {
151
+ return (db.objectStoreNames.contains(HERD_MODULES_STORE) &&
152
+ db.objectStoreNames.contains(JWT_MINTS_STORE) &&
153
+ db.objectStoreNames.contains(CACHE_METADATA_STORE));
154
+ }
155
+ async setHerdModules(herdModules, ttlMs = DEFAULT_TTL_MS, etag, userId) {
156
+ const db = await this.getDatabase(userId);
157
+ const transaction = db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
158
+ const completion = transactionComplete(transaction);
159
+ const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
160
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
161
+ const timestamp = Date.now();
162
+ herdModulesStore.clear();
163
+ herdModules.forEach((herdModule) => {
164
+ if (herdModule.herd.id == null)
165
+ return;
166
+ herdModulesStore.put({
167
+ herdId: herdModule.herd.id.toString(),
168
+ data: herdModule,
132
169
  timestamp,
133
- ttl: ttlMs,
134
- version,
135
170
  dbVersion: DB_VERSION,
136
- etag,
137
- lastModified: timestamp,
138
- };
139
- metadataStore.put(metadata);
171
+ });
140
172
  });
173
+ const metadata = {
174
+ key: "herd_modules",
175
+ timestamp,
176
+ ttl: ttlMs,
177
+ version: "2.0.0",
178
+ dbVersion: DB_VERSION,
179
+ etag,
180
+ lastModified: timestamp,
181
+ };
182
+ metadataStore.put(metadata);
183
+ await completion;
141
184
  }
142
- async getHerdModules() {
143
- await this.init();
144
- if (!this.db)
145
- throw new Error("Database not initialized");
146
- if (!this.validateDatabaseSchema()) {
147
- throw new Error("Database schema validation failed - required object stores not found");
148
- }
149
- const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readonly");
185
+ async getHerdModules(userId) {
186
+ const scopedUserId = userId ?? this.userId ?? undefined;
187
+ const db = await this.getDatabase(scopedUserId);
188
+ const transaction = db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readonly");
150
189
  return new Promise((resolve, reject) => {
151
190
  transaction.onerror = () => reject(transaction.error);
152
191
  const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
153
192
  const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
154
- // Get metadata first
155
193
  const metadataRequest = metadataStore.get("herd_modules");
156
194
  metadataRequest.onsuccess = () => {
157
195
  const metadata = metadataRequest.result;
@@ -161,12 +199,10 @@ export class ScoutCache {
161
199
  resolve({ data: null, isStale: true, age: 0, metadata: null });
162
200
  return;
163
201
  }
164
- // Check if cache is from an incompatible DB version
165
202
  if (!metadata.dbVersion || metadata.dbVersion !== DB_VERSION) {
166
203
  console.log(`[ScoutCache] Cache from incompatible DB version (${metadata.dbVersion || "unknown"} !== ${DB_VERSION}), invalidating`);
167
204
  this.stats.misses++;
168
- // Clear old cache asynchronously
169
- this.clearHerdModules().catch((error) => {
205
+ this.clearHerdModules(scopedUserId).catch((error) => {
170
206
  console.warn("[ScoutCache] Failed to clear old cache:", error);
171
207
  });
172
208
  resolve({ data: null, isStale: true, age: 0, metadata: null });
@@ -174,18 +210,16 @@ export class ScoutCache {
174
210
  }
175
211
  const age = now - metadata.timestamp;
176
212
  const isStale = age > metadata.ttl;
177
- // Get all herd modules
178
213
  const getAllRequest = herdModulesStore.getAll();
179
214
  getAllRequest.onsuccess = () => {
180
215
  const cacheEntries = getAllRequest.result;
181
216
  const herdModules = cacheEntries
182
217
  .filter((entry) => entry.data &&
183
218
  entry.data.herd &&
184
- entry.data.herd.slug &&
219
+ entry.data.herd.id != null &&
185
220
  entry.dbVersion === DB_VERSION)
186
221
  .map((entry) => entry.data)
187
222
  .sort((a, b) => (a.herd?.slug || "").localeCompare(b.herd?.slug || ""));
188
- // Update stats
189
223
  if (herdModules.length > 0) {
190
224
  this.stats.hits++;
191
225
  }
@@ -202,33 +236,21 @@ export class ScoutCache {
202
236
  };
203
237
  });
204
238
  }
205
- async setJwtMint(key, mint) {
206
- await this.init();
207
- if (!this.db)
208
- throw new Error("Database not initialized");
209
- if (!this.validateDatabaseSchema()) {
210
- throw new Error("Database schema validation failed - required object stores not found");
211
- }
212
- const transaction = this.db.transaction([JWT_MINTS_STORE], "readwrite");
213
- return new Promise((resolve, reject) => {
214
- transaction.onerror = () => reject(transaction.error);
215
- transaction.oncomplete = () => resolve();
216
- transaction.objectStore(JWT_MINTS_STORE).put({
217
- key,
218
- data: mint,
219
- timestamp: Date.now(),
220
- dbVersion: DB_VERSION,
221
- });
239
+ async setJwtMint(key, mint, userId) {
240
+ const db = await this.getDatabase(userId);
241
+ const transaction = db.transaction([JWT_MINTS_STORE], "readwrite");
242
+ const completion = transactionComplete(transaction);
243
+ transaction.objectStore(JWT_MINTS_STORE).put({
244
+ key,
245
+ data: mint,
246
+ timestamp: Date.now(),
247
+ dbVersion: DB_VERSION,
222
248
  });
249
+ await completion;
223
250
  }
224
- async getJwtMint(key) {
225
- await this.init();
226
- if (!this.db)
227
- throw new Error("Database not initialized");
228
- if (!this.validateDatabaseSchema()) {
229
- throw new Error("Database schema validation failed - required object stores not found");
230
- }
231
- const transaction = this.db.transaction([JWT_MINTS_STORE], "readonly");
251
+ async getJwtMint(key, userId) {
252
+ const db = await this.getDatabase(userId);
253
+ const transaction = db.transaction([JWT_MINTS_STORE], "readonly");
232
254
  return new Promise((resolve, reject) => {
233
255
  transaction.onerror = () => reject(transaction.error);
234
256
  const request = transaction.objectStore(JWT_MINTS_STORE).get(key);
@@ -260,58 +282,32 @@ export class ScoutCache {
260
282
  };
261
283
  });
262
284
  }
263
- async clearJwtMint(key) {
264
- await this.init();
265
- if (!this.db)
266
- throw new Error("Database not initialized");
267
- if (!this.validateDatabaseSchema()) {
268
- throw new Error("Database schema validation failed - required object stores not found");
269
- }
270
- const transaction = this.db.transaction([JWT_MINTS_STORE], "readwrite");
271
- return new Promise((resolve, reject) => {
272
- transaction.onerror = () => reject(transaction.error);
273
- transaction.oncomplete = () => resolve();
274
- transaction.objectStore(JWT_MINTS_STORE).delete(key);
275
- });
285
+ async clearJwtMint(key, userId) {
286
+ if (!userId && !this.dbName)
287
+ return;
288
+ const db = await this.getDatabase(userId);
289
+ const transaction = db.transaction([JWT_MINTS_STORE], "readwrite");
290
+ const completion = transactionComplete(transaction);
291
+ transaction.objectStore(JWT_MINTS_STORE).delete(key);
292
+ await completion;
276
293
  }
277
- async clearHerdModules() {
278
- await this.init();
279
- if (!this.db)
280
- throw new Error("Database not initialized");
281
- if (!this.validateDatabaseSchema()) {
282
- throw new Error("Database schema validation failed - required object stores not found");
283
- }
284
- const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
285
- return new Promise((resolve, reject) => {
286
- transaction.onerror = () => reject(transaction.error);
287
- transaction.oncomplete = () => resolve();
288
- const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
289
- const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
290
- herdModulesStore.clear();
291
- metadataStore.delete("herd_modules");
292
- });
294
+ async clearHerdModules(userId) {
295
+ if (!userId && !this.dbName)
296
+ return;
297
+ const db = await this.getDatabase(userId);
298
+ const transaction = db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
299
+ const completion = transactionComplete(transaction);
300
+ transaction.objectStore(HERD_MODULES_STORE).clear();
301
+ transaction.objectStore(CACHE_METADATA_STORE).delete("herd_modules");
302
+ await completion;
293
303
  }
294
- async invalidateHerdModules() {
295
- await this.init();
296
- if (!this.db)
297
- throw new Error("Database not initialized");
298
- if (!this.validateDatabaseSchema()) {
299
- throw new Error("Database schema validation failed - required object stores not found");
300
- }
301
- const transaction = this.db.transaction([CACHE_METADATA_STORE], "readwrite");
302
- return new Promise((resolve, reject) => {
303
- transaction.onerror = () => reject(transaction.error);
304
- transaction.oncomplete = () => resolve();
305
- const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
306
- metadataStore.delete("herd_modules");
307
- metadataStore.delete("providers");
308
- });
304
+ async invalidateHerdModules(userId) {
305
+ await this.clearHerdModules(userId);
309
306
  }
310
- async getCacheStats() {
311
- const result = await this.getHerdModules();
307
+ async getCacheStats(userId) {
308
+ const result = await this.getHerdModules(userId);
312
309
  const totalRequests = this.stats.hits + this.stats.misses;
313
310
  const hitRate = totalRequests > 0 ? this.stats.hits / totalRequests : 0;
314
- // Calculate size based on herd modules count (no longer including events/sessions/artifacts arrays)
315
311
  const size = result.data?.length || 0;
316
312
  return {
317
313
  size,
@@ -322,26 +318,25 @@ export class ScoutCache {
322
318
  totalMisses: this.stats.misses,
323
319
  };
324
320
  }
325
- async isCacheValid(ttlMs) {
326
- const result = await this.getHerdModules();
321
+ async isCacheValid(ttlMs, userId) {
322
+ const result = await this.getHerdModules(userId);
327
323
  if (!result.data || !result.metadata)
328
324
  return false;
329
325
  const effectiveTtl = ttlMs || result.metadata.ttl;
330
326
  return !result.isStale && result.age < effectiveTtl;
331
327
  }
332
- async getCacheAge() {
333
- const result = await this.getHerdModules();
328
+ async getCacheAge(userId) {
329
+ const result = await this.getHerdModules(userId);
334
330
  return result.age;
335
331
  }
336
- async shouldRefresh(maxAgeMs, forceRefresh) {
332
+ async shouldRefresh(maxAgeMs, forceRefresh, userId) {
337
333
  if (forceRefresh) {
338
334
  return { shouldRefresh: true, reason: "Force refresh requested" };
339
335
  }
340
- const result = await this.getHerdModules();
336
+ const result = await this.getHerdModules(userId);
341
337
  if (!result.data || result.data.length === 0) {
342
338
  return { shouldRefresh: true, reason: "No cached data" };
343
339
  }
344
- // Check for DB version mismatch
345
340
  if (!result.metadata ||
346
341
  !result.metadata.dbVersion ||
347
342
  result.metadata.dbVersion !== DB_VERSION) {
@@ -361,12 +356,12 @@ export class ScoutCache {
361
356
  }
362
357
  return { shouldRefresh: false, reason: "Cache is valid and fresh" };
363
358
  }
364
- async preloadCache(loadFunction, ttlMs = DEFAULT_TTL_MS) {
359
+ async preloadCache(loadFunction, ttlMs = DEFAULT_TTL_MS, userId) {
365
360
  try {
366
361
  console.log("[ScoutCache] Starting background cache preload...");
367
362
  const startTime = Date.now();
368
363
  const herdModules = await loadFunction();
369
- await this.setHerdModules(herdModules, ttlMs);
364
+ await this.setHerdModules(herdModules, ttlMs, undefined, userId);
370
365
  const duration = Date.now() - startTime;
371
366
  console.log(`[ScoutCache] Background preload completed in ${duration}ms`);
372
367
  }
@@ -380,66 +375,62 @@ export class ScoutCache {
380
375
  getCurrentDbVersion() {
381
376
  return DB_VERSION;
382
377
  }
383
- async isCacheVersionCompatible() {
378
+ async isCacheVersionCompatible(userId) {
384
379
  try {
385
- const result = await this.getHerdModules();
380
+ const result = await this.getHerdModules(userId);
386
381
  if (!result.metadata)
387
382
  return false;
388
- return (result.metadata.dbVersion !== undefined &&
389
- result.metadata.dbVersion === DB_VERSION);
383
+ return result.metadata.dbVersion === DB_VERSION;
390
384
  }
391
385
  catch (error) {
392
386
  console.warn("[ScoutCache] Version compatibility check failed:", error);
393
387
  return false;
394
388
  }
395
389
  }
396
- async resetDatabase() {
397
- console.log("[ScoutCache] Resetting database...");
398
- // Close existing connection
399
- if (this.db) {
400
- this.db.close();
401
- this.db = null;
390
+ async resetDatabase(userId) {
391
+ const dbName = userId ? buildDbName(userId) : this.dbName;
392
+ if (!dbName) {
393
+ return;
394
+ }
395
+ const existingDelete = this.deletePromises.get(dbName);
396
+ if (existingDelete)
397
+ return existingDelete;
398
+ const deletePromise = (async () => {
399
+ const pendingDatabase = this.initPromises.get(dbName);
400
+ const database = this.databases.get(dbName) ??
401
+ (pendingDatabase ? await pendingDatabase : null);
402
+ database?.close();
403
+ this.databases.delete(dbName);
404
+ this.initPromises.delete(dbName);
405
+ await new Promise((resolve, reject) => {
406
+ const request = indexedDB.deleteDatabase(dbName);
407
+ request.onsuccess = () => resolve();
408
+ request.onerror = () => reject(request.error);
409
+ request.onblocked = () => {
410
+ console.warn("[ScoutCache] Database reset blocked - close all other tabs");
411
+ };
412
+ });
413
+ })();
414
+ this.deletePromises.set(dbName, deletePromise);
415
+ try {
416
+ await deletePromise;
417
+ }
418
+ finally {
419
+ if (this.deletePromises.get(dbName) === deletePromise) {
420
+ this.deletePromises.delete(dbName);
421
+ }
402
422
  }
403
- this.initPromise = null;
404
- // Delete the database
405
- return new Promise((resolve, reject) => {
406
- const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
407
- deleteRequest.onsuccess = () => {
408
- console.log("[ScoutCache] Database reset successfully");
409
- resolve();
410
- };
411
- deleteRequest.onerror = () => {
412
- console.error("[ScoutCache] Failed to reset database:", deleteRequest.error);
413
- reject(deleteRequest.error);
414
- };
415
- deleteRequest.onblocked = () => {
416
- console.warn("[ScoutCache] Database reset blocked - close all other tabs");
417
- // Continue anyway, it will resolve when unblocked
418
- };
419
- });
420
423
  }
421
- async checkDatabaseHealth() {
424
+ async checkDatabaseHealth(userId) {
422
425
  const issues = [];
423
426
  try {
424
- await this.init();
425
- if (!this.db) {
426
- issues.push("Database connection not established");
427
- return { healthy: false, issues };
428
- }
429
- if (!this.validateDatabaseSchema()) {
430
- issues.push("Database schema validation failed");
431
- }
432
- // Check version compatibility
433
- const isVersionCompatible = await this.isCacheVersionCompatible();
427
+ await this.getDatabase(userId);
428
+ const isVersionCompatible = await this.isCacheVersionCompatible(userId);
434
429
  if (!isVersionCompatible) {
435
430
  issues.push(`Cache version incompatible (current: ${DB_VERSION})`);
436
431
  }
437
- // Try a simple read operation
438
432
  try {
439
- const result = await this.getHerdModules();
440
- if (result.data === null && result.age === 0) {
441
- // This is expected for empty cache, not an error
442
- }
433
+ await this.getHerdModules(userId);
443
434
  }
444
435
  catch (error) {
445
436
  issues.push(`Read operation failed: ${error}`);
@@ -453,6 +444,19 @@ export class ScoutCache {
453
444
  issues,
454
445
  };
455
446
  }
447
+ async clearAll(userId) {
448
+ deleteLegacyDatabase();
449
+ const userToClear = userId ?? this.userId;
450
+ if (!userToClear)
451
+ return;
452
+ await this.resetDatabase(userToClear);
453
+ if (this.userId === userToClear) {
454
+ this.userId = null;
455
+ this.dbName = null;
456
+ }
457
+ }
456
458
  }
457
- // Singleton instance
458
459
  export const scoutCache = new ScoutCache();
460
+ export async function clearScoutClientState(userId) {
461
+ await scoutCache.clearAll(userId);
462
+ }
@@ -0,0 +1,26 @@
1
+ import type { ComplianceResourceCreateInput, ComplianceResourceTypeCreateInput, ComplianceResourceTypeUpdateInput, ComplianceResourceUpdateInput, ContactTypeCreateInput, ContactTypeUpdateInput, HerdOperatingPermissionCreateInput, HerdOperatingPermissionUpdateInput, IComplianceResource, IComplianceResourceType, IContactType, IHerdOperatingPermission, IOperatingContextContact, IOperatingContextPointOfInterest, IOperatingContextPointOfInterestType, IOperatingPermission, OperatingContextContactCreateInput, OperatingContextContactUpdateInput, OperatingContextListOptions, OperatingContextPointOfInterestCreateInput, OperatingContextPointOfInterestTypeCreateInput, OperatingContextPointOfInterestTypeUpdateInput, OperatingContextPointOfInterestUpdateInput, OperatingPermissionCreateInput, OperatingPermissionUpdateInput } from "../types/db";
2
+ import type { IWebResponseCompatible } from "../types/requests";
3
+ export declare function server_get_contact_types_by_localization(localizationId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IContactType[]>>;
4
+ export declare function server_create_contact_type(row: ContactTypeCreateInput): Promise<IWebResponseCompatible<IContactType | null>>;
5
+ export declare function server_update_contact_type(id: number, updates: ContactTypeUpdateInput): Promise<IWebResponseCompatible<IContactType | null>>;
6
+ export declare function server_get_operating_context_point_of_interest_types_by_localization(localizationId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IOperatingContextPointOfInterestType[]>>;
7
+ export declare function server_create_operating_context_point_of_interest_type(row: OperatingContextPointOfInterestTypeCreateInput): Promise<IWebResponseCompatible<IOperatingContextPointOfInterestType | null>>;
8
+ export declare function server_update_operating_context_point_of_interest_type(id: number, updates: OperatingContextPointOfInterestTypeUpdateInput): Promise<IWebResponseCompatible<IOperatingContextPointOfInterestType | null>>;
9
+ export declare function server_get_operating_permissions_by_localization(localizationId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IOperatingPermission[]>>;
10
+ export declare function server_create_operating_permission(row: OperatingPermissionCreateInput): Promise<IWebResponseCompatible<IOperatingPermission | null>>;
11
+ export declare function server_update_operating_permission(id: number, updates: OperatingPermissionUpdateInput): Promise<IWebResponseCompatible<IOperatingPermission | null>>;
12
+ export declare function server_get_compliance_resource_types(options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IComplianceResourceType[]>>;
13
+ export declare function server_create_compliance_resource_type(row: ComplianceResourceTypeCreateInput): Promise<IWebResponseCompatible<IComplianceResourceType | null>>;
14
+ export declare function server_update_compliance_resource_type(id: number, updates: ComplianceResourceTypeUpdateInput): Promise<IWebResponseCompatible<IComplianceResourceType | null>>;
15
+ export declare function server_get_compliance_resources_by_localization(localizationId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IComplianceResource[]>>;
16
+ export declare function server_create_compliance_resource(row: ComplianceResourceCreateInput): Promise<IWebResponseCompatible<IComplianceResource | null>>;
17
+ export declare function server_update_compliance_resource(id: number, updates: ComplianceResourceUpdateInput): Promise<IWebResponseCompatible<IComplianceResource | null>>;
18
+ export declare function server_get_operating_context_points_of_interest_by_operating_context(operatingContextId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IOperatingContextPointOfInterest[]>>;
19
+ export declare function server_create_operating_context_point_of_interest(row: OperatingContextPointOfInterestCreateInput): Promise<IWebResponseCompatible<IOperatingContextPointOfInterest | null>>;
20
+ export declare function server_update_operating_context_point_of_interest(id: number, updates: OperatingContextPointOfInterestUpdateInput): Promise<IWebResponseCompatible<IOperatingContextPointOfInterest | null>>;
21
+ export declare function server_get_operating_context_contacts_by_operating_context(operatingContextId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IOperatingContextContact[]>>;
22
+ export declare function server_create_operating_context_contact(row: OperatingContextContactCreateInput): Promise<IWebResponseCompatible<IOperatingContextContact | null>>;
23
+ export declare function server_update_operating_context_contact(id: number, updates: OperatingContextContactUpdateInput): Promise<IWebResponseCompatible<IOperatingContextContact | null>>;
24
+ export declare function server_get_herd_operating_permissions_by_herd(herdId: number, options?: OperatingContextListOptions): Promise<IWebResponseCompatible<IHerdOperatingPermission[]>>;
25
+ export declare function server_create_herd_operating_permission(row: HerdOperatingPermissionCreateInput): Promise<IWebResponseCompatible<IHerdOperatingPermission | null>>;
26
+ export declare function server_update_herd_operating_permission(id: number, updates: HerdOperatingPermissionUpdateInput): Promise<IWebResponseCompatible<IHerdOperatingPermission | null>>;