@adventurelabs/scout-core 1.0.109 → 1.0.110

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,12 +1,12 @@
1
- import { useEffect, useCallback, useRef } from "react";
1
+ import { useEffect, useCallback, useRef, useMemo } from "react";
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useStore } from "react-redux";
4
4
  import { EnumScoutStateStatus, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setUser, setDataSource, setDataSourceInfo, } from "../store/scout";
5
5
  import { EnumHerdModulesLoadingState } from "../types/herd_module";
6
6
  import { server_load_herd_modules } from "../helpers/herds";
7
- import { server_get_user } from "../helpers/users";
8
7
  import { scoutCache } from "../helpers/cache";
9
8
  import { EnumDataSource } from "../types/data_source";
9
+ import { createBrowserClient } from "@supabase/ssr";
10
10
  /**
11
11
  * Hook for refreshing scout data with detailed timing measurements and cache-first loading
12
12
  *
@@ -36,6 +36,10 @@ export function useScoutRefresh(options = {}) {
36
36
  const dispatch = useAppDispatch();
37
37
  const store = useStore();
38
38
  const refreshInProgressRef = useRef(false);
39
+ // Create Supabase client directly to avoid circular dependency
40
+ const supabase = useMemo(() => {
41
+ return createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL || "", process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "");
42
+ }, []);
39
43
  // Refs to store timing measurements
40
44
  const timingRefs = useRef({
41
45
  startTime: 0,
@@ -87,18 +91,37 @@ export function useScoutRefresh(options = {}) {
87
91
  }
88
92
  return true;
89
93
  }, []);
90
- // Helper function to conditionally dispatch only if data has changed
91
- const conditionalDispatch = useCallback((newData, currentData, actionCreator, dataType) => {
92
- if (!deepEqual(newData, currentData)) {
93
- console.log(`[useScoutRefresh] ${dataType} data changed, updating store`);
94
- dispatch(actionCreator(newData));
94
+ // Helper function to normalize herd modules for comparison (excludes metadata)
95
+ const normalizeHerdModulesForComparison = useCallback((herdModules) => {
96
+ if (!Array.isArray(herdModules))
97
+ return herdModules;
98
+ return herdModules.map((hm) => {
99
+ if (!hm || typeof hm !== "object")
100
+ return hm;
101
+ // Create a copy without metadata fields that don't represent business data changes
102
+ const { timestamp_last_refreshed, ...businessData } = hm;
103
+ return businessData;
104
+ });
105
+ }, []);
106
+ // Helper function to conditionally dispatch only if business data has changed
107
+ const conditionalDispatch = useCallback((newData, currentData, actionCreator, dataType, useNormalizedComparison = false) => {
108
+ let dataToCompare = newData;
109
+ let currentToCompare = currentData;
110
+ // For herd modules, use normalized comparison that ignores metadata
111
+ if (useNormalizedComparison && dataType.includes("Herd modules")) {
112
+ dataToCompare = normalizeHerdModulesForComparison(newData);
113
+ currentToCompare = normalizeHerdModulesForComparison(currentData);
114
+ }
115
+ if (!deepEqual(dataToCompare, currentToCompare)) {
116
+ console.log(`[useScoutRefresh] ${dataType} business data changed, updating store`);
117
+ dispatch(actionCreator(newData)); // Always dispatch the full data including timestamps
95
118
  return true;
96
119
  }
97
120
  else {
98
- console.log(`[useScoutRefresh] ${dataType} data unchanged, skipping store update`);
121
+ console.log(`[useScoutRefresh] ${dataType} business data unchanged, skipping store update`);
99
122
  return false;
100
123
  }
101
- }, [dispatch, deepEqual]);
124
+ }, [dispatch, deepEqual, normalizeHerdModulesForComparison]);
102
125
  // Helper function to handle IndexedDB errors - memoized for stability
103
126
  const handleIndexedDbError = useCallback(async (error, operation, retryFn) => {
104
127
  if (error instanceof Error &&
@@ -151,23 +174,13 @@ export function useScoutRefresh(options = {}) {
151
174
  cacheAge: cacheResult.age,
152
175
  isStale: cacheResult.isStale,
153
176
  }));
154
- // Conditionally update the store with cached data if different
177
+ // Conditionally update the store with cached data if business data is different
155
178
  // Get current state at execution time to avoid dependency issues
156
179
  const currentHerdModules = store.getState().scout.herd_modules;
157
- const herdModulesChanged = conditionalDispatch(cachedHerdModules, currentHerdModules, setHerdModules, "Herd modules (cache)");
180
+ const herdModulesChanged = conditionalDispatch(cachedHerdModules, currentHerdModules, setHerdModules, "Herd modules (cache)", true);
158
181
  if (herdModulesChanged) {
159
182
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
160
183
  }
161
- // Always load user data from API
162
- const userStartTime = Date.now();
163
- const res_new_user = await server_get_user();
164
- const userApiDuration = Date.now() - userStartTime;
165
- timingRefs.current.userApiDuration = userApiDuration;
166
- dispatch(setUserApiDuration(userApiDuration));
167
- if (res_new_user && res_new_user.data) {
168
- const currentUser = store.getState().scout.user;
169
- conditionalDispatch(res_new_user.data, currentUser, setUser, "User (initial)");
170
- }
171
184
  // If cache is fresh, we still background fetch but don't wait
172
185
  if (!cacheResult.isStale) {
173
186
  console.log("[useScoutRefresh] Cache is fresh, background fetching fresh data...");
@@ -177,8 +190,22 @@ export function useScoutRefresh(options = {}) {
177
190
  console.log("[useScoutRefresh] Starting background fetch...");
178
191
  const backgroundStartTime = Date.now();
179
192
  const [backgroundHerdModulesResult, backgroundUserResult] = await Promise.all([
180
- server_load_herd_modules(),
181
- server_get_user(),
193
+ (async () => {
194
+ const start = Date.now();
195
+ const result = await server_load_herd_modules();
196
+ const duration = Date.now() - start;
197
+ timingRefs.current.herdModulesDuration = duration;
198
+ dispatch(setHerdModulesApiDuration(duration));
199
+ return result;
200
+ })(),
201
+ (async () => {
202
+ const start = Date.now();
203
+ const { data } = await supabase.auth.getUser();
204
+ const duration = Date.now() - start;
205
+ timingRefs.current.userApiDuration = duration;
206
+ dispatch(setUserApiDuration(duration));
207
+ return { data: data.user, status: "success" };
208
+ })(),
182
209
  ]);
183
210
  const backgroundDuration = Date.now() - backgroundStartTime;
184
211
  console.log(`[useScoutRefresh] Background fetch completed in ${backgroundDuration}ms`);
@@ -200,11 +227,13 @@ export function useScoutRefresh(options = {}) {
200
227
  }
201
228
  });
202
229
  }
203
- // Conditionally update store with fresh background data
230
+ // Conditionally update store with fresh background data using normalized comparison
204
231
  const currentHerdModules = store.getState().scout.herd_modules;
205
232
  const currentUser = store.getState().scout.user;
206
- conditionalDispatch(backgroundHerdModulesResult.data, currentHerdModules, setHerdModules, "Herd modules (background)");
207
- conditionalDispatch(backgroundUserResult.data, currentUser, setUser, "User (background)");
233
+ conditionalDispatch(backgroundHerdModulesResult.data, currentHerdModules, setHerdModules, "Herd modules (background)", true);
234
+ if (backgroundUserResult && backgroundUserResult.data) {
235
+ conditionalDispatch(backgroundUserResult.data, currentUser, setUser, "User (background)");
236
+ }
208
237
  // Update data source to DATABASE
209
238
  dispatch(setDataSource(EnumDataSource.DATABASE));
210
239
  dispatch(setDataSourceInfo({
@@ -254,10 +283,14 @@ export function useScoutRefresh(options = {}) {
254
283
  (async () => {
255
284
  const start = Date.now();
256
285
  console.log(`[useScoutRefresh] Starting user request at ${new Date(start).toISOString()}`);
257
- const result = await server_get_user();
286
+ const { data } = await supabase.auth.getUser();
258
287
  const duration = Date.now() - start;
259
288
  console.log(`[useScoutRefresh] User request completed in ${duration}ms`);
260
- return { result, duration, start };
289
+ return {
290
+ result: { data: data.user, status: "success" },
291
+ duration,
292
+ start,
293
+ };
261
294
  })(),
262
295
  ]);
263
296
  const parallelDuration = Date.now() - parallelStartTime;
@@ -306,11 +339,11 @@ export function useScoutRefresh(options = {}) {
306
339
  await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs);
307
340
  });
308
341
  }
309
- // Step 4: Conditionally update store with fresh data if different
342
+ // Step 4: Conditionally update store with fresh data using normalized comparison
310
343
  const dataProcessingStartTime = Date.now();
311
344
  const currentHerdModules = store.getState().scout.herd_modules;
312
345
  const currentUser = store.getState().scout.user;
313
- const herdModulesChanged = conditionalDispatch(compatible_new_herd_modules, currentHerdModules, setHerdModules, "Herd modules (fresh API)");
346
+ const herdModulesChanged = conditionalDispatch(compatible_new_herd_modules, currentHerdModules, setHerdModules, "Herd modules (fresh API)", true);
314
347
  const userChanged = conditionalDispatch(res_new_user.data, currentUser, setUser, "User (fresh API)");
315
348
  if (herdModulesChanged) {
316
349
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
@@ -359,6 +392,7 @@ export function useScoutRefresh(options = {}) {
359
392
  }, [
360
393
  dispatch,
361
394
  store,
395
+ supabase,
362
396
  onRefreshComplete,
363
397
  cacheFirst,
364
398
  cacheTtlMs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.109",
3
+ "version": "1.0.110",
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",