@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.
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/helpers/cache.d.ts +25 -17
- package/dist/helpers/cache.js +225 -221
- package/dist/helpers/compliance.d.ts +26 -0
- package/dist/helpers/compliance.js +74 -0
- package/dist/helpers/compliance.queries.d.ts +28 -0
- package/dist/helpers/compliance.queries.js +167 -0
- package/dist/helpers/contacts_server.d.ts +12 -0
- package/dist/helpers/lifecycle_server.d.ts +3 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/useScoutRealtimeOperatingContexts.d.ts +5 -3
- package/dist/hooks/useScoutRealtimeOperatingContexts.js +8 -0
- package/dist/hooks/useScoutRefresh.d.ts +3 -2
- package/dist/hooks/useScoutRefresh.js +160 -123
- package/dist/providers/ScoutRefreshProvider.js +39 -17
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/store/scout.d.ts +1 -3
- package/dist/store/scout.js +9 -14
- package/dist/types/db.d.ts +45 -1
- package/dist/types/supabase.d.ts +512 -0
- package/package.json +1 -1
package/dist/helpers/cache.js
CHANGED
|
@@ -1,52 +1,114 @@
|
|
|
1
|
-
const
|
|
2
|
-
const DB_VERSION =
|
|
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.
|
|
11
|
-
this.
|
|
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
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const transaction =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
226
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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.
|
|
425
|
-
|
|
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
|
-
|
|
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>>;
|