@adventurelabs/scout-core 1.2.5 → 1.3.1

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.
@@ -0,0 +1,360 @@
1
+ import { useState, useCallback, useMemo, useEffect } from "react";
2
+ import { useGetSessionsInfiniteByHerdQuery, useGetSessionsInfiniteByDeviceQuery, useGetEventsInfiniteByHerdQuery, useGetEventsInfiniteByDeviceQuery, useGetArtifactsInfiniteByHerdQuery, useGetArtifactsInfiniteByDeviceQuery, } from "../store/api";
3
+ // =====================================================
4
+ // SESSIONS INFINITE SCROLL HOOKS
5
+ // =====================================================
6
+ export const useInfiniteSessionsByHerd = (herdId, options) => {
7
+ const [pages, setPages] = useState([]);
8
+ const [currentCursor, setCurrentCursor] = useState(null);
9
+ const currentQuery = useGetSessionsInfiniteByHerdQuery({
10
+ herdId,
11
+ limit: options.limit || 20,
12
+ cursor: currentCursor,
13
+ supabase: options.supabase,
14
+ }, {
15
+ skip: !options.enabled,
16
+ });
17
+ // Update pages when new data arrives
18
+ useEffect(() => {
19
+ if (currentQuery.data && !currentQuery.isLoading) {
20
+ setPages((prev) => {
21
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
22
+ (p.cursor &&
23
+ currentCursor &&
24
+ p.cursor.id === currentCursor.id &&
25
+ p.cursor.timestamp === currentCursor.timestamp));
26
+ if (!existingPage) {
27
+ return [
28
+ ...prev,
29
+ { cursor: currentCursor, data: currentQuery.data.sessions },
30
+ ];
31
+ }
32
+ return prev;
33
+ });
34
+ }
35
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
36
+ const loadMore = useCallback(() => {
37
+ if (currentQuery.data?.hasMore &&
38
+ currentQuery.data.nextCursor &&
39
+ !currentQuery.isLoading) {
40
+ setCurrentCursor(currentQuery.data.nextCursor);
41
+ }
42
+ }, [currentQuery.data, currentQuery.isLoading]);
43
+ const refetch = useCallback(() => {
44
+ setPages([]);
45
+ setCurrentCursor(null);
46
+ currentQuery.refetch();
47
+ }, [currentQuery]);
48
+ // Flatten all pages into single array
49
+ const allItems = useMemo(() => {
50
+ return pages.flatMap((page) => page.data);
51
+ }, [pages]);
52
+ return {
53
+ items: allItems,
54
+ isLoading: currentQuery.isLoading && pages.length === 0,
55
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
56
+ hasMore: currentQuery.data?.hasMore || false,
57
+ loadMore,
58
+ refetch,
59
+ error: currentQuery.error,
60
+ };
61
+ };
62
+ export const useInfiniteSessionsByDevice = (deviceId, options) => {
63
+ const [pages, setPages] = useState([]);
64
+ const [currentCursor, setCurrentCursor] = useState(null);
65
+ const currentQuery = useGetSessionsInfiniteByDeviceQuery({
66
+ deviceId,
67
+ limit: options.limit || 20,
68
+ cursor: currentCursor,
69
+ supabase: options.supabase,
70
+ }, {
71
+ skip: !options.enabled,
72
+ });
73
+ useEffect(() => {
74
+ if (currentQuery.data && !currentQuery.isLoading) {
75
+ setPages((prev) => {
76
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
77
+ (p.cursor &&
78
+ currentCursor &&
79
+ p.cursor.id === currentCursor.id &&
80
+ p.cursor.timestamp === currentCursor.timestamp));
81
+ if (!existingPage) {
82
+ return [
83
+ ...prev,
84
+ { cursor: currentCursor, data: currentQuery.data.sessions },
85
+ ];
86
+ }
87
+ return prev;
88
+ });
89
+ }
90
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
91
+ const loadMore = useCallback(() => {
92
+ if (currentQuery.data?.hasMore &&
93
+ currentQuery.data.nextCursor &&
94
+ !currentQuery.isLoading) {
95
+ setCurrentCursor(currentQuery.data.nextCursor);
96
+ }
97
+ }, [currentQuery.data, currentQuery.isLoading]);
98
+ const refetch = useCallback(() => {
99
+ setPages([]);
100
+ setCurrentCursor(null);
101
+ currentQuery.refetch();
102
+ }, [currentQuery]);
103
+ const allItems = useMemo(() => {
104
+ return pages.flatMap((page) => page.data);
105
+ }, [pages]);
106
+ return {
107
+ items: allItems,
108
+ isLoading: currentQuery.isLoading && pages.length === 0,
109
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
110
+ hasMore: currentQuery.data?.hasMore || false,
111
+ loadMore,
112
+ refetch,
113
+ error: currentQuery.error,
114
+ };
115
+ };
116
+ // =====================================================
117
+ // EVENTS INFINITE SCROLL HOOKS
118
+ // =====================================================
119
+ export const useInfiniteEventsByHerd = (herdId, options) => {
120
+ const [pages, setPages] = useState([]);
121
+ const [currentCursor, setCurrentCursor] = useState(null);
122
+ const currentQuery = useGetEventsInfiniteByHerdQuery({
123
+ herdId,
124
+ limit: options.limit || 20,
125
+ cursor: currentCursor,
126
+ supabase: options.supabase,
127
+ }, {
128
+ skip: !options.enabled,
129
+ });
130
+ useEffect(() => {
131
+ if (currentQuery.data && !currentQuery.isLoading) {
132
+ setPages((prev) => {
133
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
134
+ (p.cursor &&
135
+ currentCursor &&
136
+ p.cursor.id === currentCursor.id &&
137
+ p.cursor.timestamp === currentCursor.timestamp));
138
+ if (!existingPage) {
139
+ return [
140
+ ...prev,
141
+ { cursor: currentCursor, data: currentQuery.data.events },
142
+ ];
143
+ }
144
+ return prev;
145
+ });
146
+ }
147
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
148
+ const loadMore = useCallback(() => {
149
+ if (currentQuery.data?.hasMore &&
150
+ currentQuery.data.nextCursor &&
151
+ !currentQuery.isLoading) {
152
+ setCurrentCursor(currentQuery.data.nextCursor);
153
+ }
154
+ }, [currentQuery.data, currentQuery.isLoading]);
155
+ const refetch = useCallback(() => {
156
+ setPages([]);
157
+ setCurrentCursor(null);
158
+ currentQuery.refetch();
159
+ }, [currentQuery]);
160
+ const allItems = useMemo(() => {
161
+ return pages.flatMap((page) => page.data);
162
+ }, [pages]);
163
+ return {
164
+ items: allItems,
165
+ isLoading: currentQuery.isLoading && pages.length === 0,
166
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
167
+ hasMore: currentQuery.data?.hasMore || false,
168
+ loadMore,
169
+ refetch,
170
+ error: currentQuery.error,
171
+ };
172
+ };
173
+ export const useInfiniteEventsByDevice = (deviceId, options) => {
174
+ const [pages, setPages] = useState([]);
175
+ const [currentCursor, setCurrentCursor] = useState(null);
176
+ const currentQuery = useGetEventsInfiniteByDeviceQuery({
177
+ deviceId,
178
+ limit: options.limit || 20,
179
+ cursor: currentCursor,
180
+ supabase: options.supabase,
181
+ }, {
182
+ skip: !options.enabled,
183
+ });
184
+ useEffect(() => {
185
+ if (currentQuery.data && !currentQuery.isLoading) {
186
+ setPages((prev) => {
187
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
188
+ (p.cursor &&
189
+ currentCursor &&
190
+ p.cursor.id === currentCursor.id &&
191
+ p.cursor.timestamp === currentCursor.timestamp));
192
+ if (!existingPage) {
193
+ return [
194
+ ...prev,
195
+ { cursor: currentCursor, data: currentQuery.data.events },
196
+ ];
197
+ }
198
+ return prev;
199
+ });
200
+ }
201
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
202
+ const loadMore = useCallback(() => {
203
+ if (currentQuery.data?.hasMore &&
204
+ currentQuery.data.nextCursor &&
205
+ !currentQuery.isLoading) {
206
+ setCurrentCursor(currentQuery.data.nextCursor);
207
+ }
208
+ }, [currentQuery.data, currentQuery.isLoading]);
209
+ const refetch = useCallback(() => {
210
+ setPages([]);
211
+ setCurrentCursor(null);
212
+ currentQuery.refetch();
213
+ }, [currentQuery]);
214
+ const allItems = useMemo(() => {
215
+ return pages.flatMap((page) => page.data);
216
+ }, [pages]);
217
+ return {
218
+ items: allItems,
219
+ isLoading: currentQuery.isLoading && pages.length === 0,
220
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
221
+ hasMore: currentQuery.data?.hasMore || false,
222
+ loadMore,
223
+ refetch,
224
+ error: currentQuery.error,
225
+ };
226
+ };
227
+ // =====================================================
228
+ // ARTIFACTS INFINITE SCROLL HOOKS
229
+ // =====================================================
230
+ export const useInfiniteArtifactsByHerd = (herdId, options) => {
231
+ const [pages, setPages] = useState([]);
232
+ const [currentCursor, setCurrentCursor] = useState(null);
233
+ const currentQuery = useGetArtifactsInfiniteByHerdQuery({
234
+ herdId,
235
+ limit: options.limit || 20,
236
+ cursor: currentCursor,
237
+ supabase: options.supabase,
238
+ }, {
239
+ skip: !options.enabled,
240
+ });
241
+ useEffect(() => {
242
+ if (currentQuery.data && !currentQuery.isLoading) {
243
+ setPages((prev) => {
244
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
245
+ (p.cursor &&
246
+ currentCursor &&
247
+ p.cursor.id === currentCursor.id &&
248
+ p.cursor.timestamp === currentCursor.timestamp));
249
+ if (!existingPage) {
250
+ return [
251
+ ...prev,
252
+ { cursor: currentCursor, data: currentQuery.data.artifacts },
253
+ ];
254
+ }
255
+ return prev;
256
+ });
257
+ }
258
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
259
+ const loadMore = useCallback(() => {
260
+ if (currentQuery.data?.hasMore &&
261
+ currentQuery.data.nextCursor &&
262
+ !currentQuery.isLoading) {
263
+ setCurrentCursor(currentQuery.data.nextCursor);
264
+ }
265
+ }, [currentQuery.data, currentQuery.isLoading]);
266
+ const refetch = useCallback(() => {
267
+ setPages([]);
268
+ setCurrentCursor(null);
269
+ currentQuery.refetch();
270
+ }, [currentQuery]);
271
+ const allItems = useMemo(() => {
272
+ return pages.flatMap((page) => page.data);
273
+ }, [pages]);
274
+ return {
275
+ items: allItems,
276
+ isLoading: currentQuery.isLoading && pages.length === 0,
277
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
278
+ hasMore: currentQuery.data?.hasMore || false,
279
+ loadMore,
280
+ refetch,
281
+ error: currentQuery.error,
282
+ };
283
+ };
284
+ export const useInfiniteArtifactsByDevice = (deviceId, options) => {
285
+ const [pages, setPages] = useState([]);
286
+ const [currentCursor, setCurrentCursor] = useState(null);
287
+ const currentQuery = useGetArtifactsInfiniteByDeviceQuery({
288
+ deviceId,
289
+ limit: options.limit || 20,
290
+ cursor: currentCursor,
291
+ supabase: options.supabase,
292
+ }, {
293
+ skip: !options.enabled,
294
+ });
295
+ useEffect(() => {
296
+ if (currentQuery.data && !currentQuery.isLoading) {
297
+ setPages((prev) => {
298
+ const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) ||
299
+ (p.cursor &&
300
+ currentCursor &&
301
+ p.cursor.id === currentCursor.id &&
302
+ p.cursor.timestamp === currentCursor.timestamp));
303
+ if (!existingPage) {
304
+ return [
305
+ ...prev,
306
+ { cursor: currentCursor, data: currentQuery.data.artifacts },
307
+ ];
308
+ }
309
+ return prev;
310
+ });
311
+ }
312
+ }, [currentQuery.data, currentQuery.isLoading, currentCursor]);
313
+ const loadMore = useCallback(() => {
314
+ if (currentQuery.data?.hasMore &&
315
+ currentQuery.data.nextCursor &&
316
+ !currentQuery.isLoading) {
317
+ setCurrentCursor(currentQuery.data.nextCursor);
318
+ }
319
+ }, [currentQuery.data, currentQuery.isLoading]);
320
+ const refetch = useCallback(() => {
321
+ setPages([]);
322
+ setCurrentCursor(null);
323
+ currentQuery.refetch();
324
+ }, [currentQuery]);
325
+ const allItems = useMemo(() => {
326
+ return pages.flatMap((page) => page.data);
327
+ }, [pages]);
328
+ return {
329
+ items: allItems,
330
+ isLoading: currentQuery.isLoading && pages.length === 0,
331
+ isLoadingMore: currentQuery.isLoading && pages.length > 0,
332
+ hasMore: currentQuery.data?.hasMore || false,
333
+ loadMore,
334
+ refetch,
335
+ error: currentQuery.error,
336
+ };
337
+ };
338
+ // =====================================================
339
+ // INTERSECTION OBSERVER HOOK FOR AUTO-LOADING
340
+ // =====================================================
341
+ export const useIntersectionObserver = (callback, options = {}) => {
342
+ const [element, setElement] = useState(null);
343
+ useEffect(() => {
344
+ if (!element)
345
+ return;
346
+ const observer = new IntersectionObserver((entries) => {
347
+ const first = entries[0];
348
+ if (first.isIntersecting) {
349
+ callback();
350
+ }
351
+ }, { threshold: 0.1, ...options });
352
+ observer.observe(element);
353
+ return () => {
354
+ if (element) {
355
+ observer.unobserve(element);
356
+ }
357
+ };
358
+ }, [element, callback, options]);
359
+ return setElement;
360
+ };
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { IEventAndTagsPrettyLocation } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeEvents(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<IEventAndTagsPrettyLocation> | null, () => void];
5
+ export declare function useScoutRealtimeEvents(scoutSupabase: SupabaseClient<Database>, invalidateRTKQuery?: boolean): [RealtimeData<IEventAndTagsPrettyLocation> | null, () => void];
@@ -2,9 +2,9 @@
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useSelector } from "react-redux";
4
4
  import { useEffect, useRef, useCallback, useState } from "react";
5
- import { updateEventValuesForHerdModule } from "../store/scout";
6
5
  import { EnumRealtimeOperation } from "../types/realtime";
7
- export function useScoutRealtimeEvents(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
6
+ import { scoutApi } from "../store/api";
7
+ export function useScoutRealtimeEvents(scoutSupabase, invalidateRTKQuery = true) {
8
8
  const channels = useRef([]);
9
9
  const dispatch = useAppDispatch();
10
10
  const [latestEventUpdate, setLatestEventUpdate] = useState(null);
@@ -17,37 +17,32 @@ export function useScoutRealtimeEvents(scoutSupabase, shouldUpdateGlobalStateOnC
17
17
  if (!eventData)
18
18
  return;
19
19
  let operation;
20
- // TODO: UNCOMMENT GLOBAL STORE OPERATIONS IF OKAY WITH FREQUENT
21
20
  switch (data.operation) {
22
21
  case "INSERT":
23
22
  operation = EnumRealtimeOperation.INSERT;
24
- if (data.record && activeHerdId && shouldUpdateGlobalStateOnChanges) {
25
- console.log("[Events] New event received:", data.record);
26
- // For events, we need to update the event values in the herd module
27
- dispatch(updateEventValuesForHerdModule({
28
- herd_id: activeHerdId,
29
- events: [data.record],
30
- }));
23
+ if (data.record && invalidateRTKQuery) {
24
+ console.log("[Events] New event received, invalidating RTK Query cache:", data.record);
25
+ // Invalidate all events queries to refetch fresh data
26
+ dispatch(scoutApi.util.invalidateTags(["Event"]));
31
27
  }
32
28
  break;
33
29
  case "UPDATE":
34
30
  operation = EnumRealtimeOperation.UPDATE;
35
- if (data.record && activeHerdId && shouldUpdateGlobalStateOnChanges) {
36
- console.log("[Events] Event updated:", data.record);
37
- dispatch(updateEventValuesForHerdModule({
38
- herd_id: activeHerdId,
39
- events: [data.record],
40
- }));
31
+ if (data.record && invalidateRTKQuery) {
32
+ console.log("[Events] Event updated, invalidating RTK Query cache:", data.record);
33
+ // Invalidate specific event and list queries
34
+ dispatch(scoutApi.util.invalidateTags([
35
+ { type: "Event", id: data.record.id || "unknown" },
36
+ { type: "Event", id: "LIST" },
37
+ ]));
41
38
  }
42
39
  break;
43
40
  case "DELETE":
44
41
  operation = EnumRealtimeOperation.DELETE;
45
- if (data.old_record &&
46
- activeHerdId &&
47
- shouldUpdateGlobalStateOnChanges) {
48
- console.log("[Events] Event deleted:", data.old_record);
49
- // TODO: WRITE DELETION STORE ACTION
50
- console.log("[Events] Event deletion detected - manual refresh may be needed");
42
+ if (data.old_record && invalidateRTKQuery) {
43
+ console.log("[Events] Event deleted, invalidating RTK Query cache:", data.old_record);
44
+ // Invalidate all events queries since item was deleted
45
+ dispatch(scoutApi.util.invalidateTags(["Event"]));
51
46
  }
52
47
  break;
53
48
  default:
@@ -59,7 +54,7 @@ export function useScoutRealtimeEvents(scoutSupabase, shouldUpdateGlobalStateOnC
59
54
  };
60
55
  console.log(`[scout-core realtime] EVENT ${data.operation} received:`, JSON.stringify(realtimeData));
61
56
  setLatestEventUpdate(realtimeData);
62
- }, [dispatch, activeHerdId]);
57
+ }, [dispatch, invalidateRTKQuery]);
63
58
  // Clear latest update
64
59
  const clearLatestUpdate = useCallback(() => {
65
60
  setLatestEventUpdate(null);
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { ISessionWithCoordinates } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeSessions(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<ISessionWithCoordinates> | null, () => void];
5
+ export declare function useScoutRealtimeSessions(scoutSupabase: SupabaseClient<Database>, invalidateRTKQuery?: boolean): [RealtimeData<ISessionWithCoordinates> | null, () => void];
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { useAppDispatch } from "../store/hooks";
3
2
  import { useSelector } from "react-redux";
3
+ import { useAppDispatch } from "../store/hooks";
4
4
  import { useEffect, useRef, useCallback, useState } from "react";
5
- import { addSessionToStore, deleteSessionFromStore, updateSessionInStore, } from "../store/scout";
6
5
  import { EnumRealtimeOperation } from "../types/realtime";
7
- export function useScoutRealtimeSessions(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
6
+ import { scoutApi } from "../store/api";
7
+ export function useScoutRealtimeSessions(scoutSupabase, invalidateRTKQuery = true) {
8
8
  const channels = useRef([]);
9
9
  const dispatch = useAppDispatch();
10
10
  const [latestSessionUpdate, setLatestSessionUpdate] = useState(null);
@@ -20,21 +20,29 @@ export function useScoutRealtimeSessions(scoutSupabase, shouldUpdateGlobalStateO
20
20
  switch (data.operation) {
21
21
  case "INSERT":
22
22
  operation = EnumRealtimeOperation.INSERT;
23
- if (data.record && shouldUpdateGlobalStateOnChanges) {
24
- console.log("[Sessions] New session received:", data.record);
25
- dispatch(addSessionToStore(data.record));
23
+ if (data.record && invalidateRTKQuery) {
24
+ console.log("[Sessions] New session received, invalidating RTK Query cache:", data.record);
25
+ // Invalidate all sessions queries to refetch fresh data
26
+ dispatch(scoutApi.util.invalidateTags(["Session"]));
26
27
  }
27
28
  break;
28
29
  case "UPDATE":
29
30
  operation = EnumRealtimeOperation.UPDATE;
30
- if (data.record && shouldUpdateGlobalStateOnChanges) {
31
- dispatch(updateSessionInStore(data.record));
31
+ if (data.record && invalidateRTKQuery) {
32
+ console.log("[Sessions] Session updated, invalidating RTK Query cache:", data.record);
33
+ // Invalidate specific session and list queries
34
+ dispatch(scoutApi.util.invalidateTags([
35
+ { type: "Session", id: data.record.id || "unknown" },
36
+ { type: "Session", id: "LIST" },
37
+ ]));
32
38
  }
33
39
  break;
34
40
  case "DELETE":
35
41
  operation = EnumRealtimeOperation.DELETE;
36
- if (data.old_record && shouldUpdateGlobalStateOnChanges) {
37
- dispatch(deleteSessionFromStore(data.old_record));
42
+ if (data.old_record && invalidateRTKQuery) {
43
+ console.log("[Sessions] Session deleted, invalidating RTK Query cache:", data.old_record);
44
+ // Invalidate all sessions queries since item was deleted
45
+ dispatch(scoutApi.util.invalidateTags(["Session"]));
38
46
  }
39
47
  break;
40
48
  default:
@@ -46,7 +54,7 @@ export function useScoutRealtimeSessions(scoutSupabase, shouldUpdateGlobalStateO
46
54
  };
47
55
  console.log(`[scout-core realtime] SESSION ${data.operation} received:`, JSON.stringify(realtimeData));
48
56
  setLatestSessionUpdate(realtimeData);
49
- }, [dispatch]);
57
+ }, [invalidateRTKQuery, dispatch]);
50
58
  // Clear latest update
51
59
  const clearLatestUpdate = useCallback(() => {
52
60
  setLatestSessionUpdate(null);
@@ -78,6 +86,6 @@ export function useScoutRealtimeSessions(scoutSupabase, shouldUpdateGlobalStateO
78
86
  channels.current.push(channel);
79
87
  }
80
88
  return cleanupChannels;
81
- }, [activeHerdId, clearLatestUpdate]);
89
+ }, [activeHerdId, clearLatestUpdate, handleSessionBroadcast]);
82
90
  return [latestSessionUpdate, clearLatestUpdate];
83
91
  }
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { ITagPrettyLocation } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeTags(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<ITagPrettyLocation> | null, () => void];
5
+ export declare function useScoutRealtimeTags(scoutSupabase: SupabaseClient<Database>, invalidateRTKQuery?: boolean): [RealtimeData<ITagPrettyLocation> | null, () => void];
@@ -2,9 +2,9 @@
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useSelector } from "react-redux";
4
4
  import { useEffect, useRef, useCallback, useState } from "react";
5
- import { addTag, deleteTag, updateTag } from "../store/scout";
6
5
  import { EnumRealtimeOperation } from "../types/realtime";
7
- export function useScoutRealtimeTags(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
6
+ import { scoutApi } from "../store/api";
7
+ export function useScoutRealtimeTags(scoutSupabase, invalidateRTKQuery = true) {
8
8
  const channels = useRef([]);
9
9
  const dispatch = useAppDispatch();
10
10
  const [latestTagUpdate, setLatestTagUpdate] = useState(null);
@@ -20,23 +20,26 @@ export function useScoutRealtimeTags(scoutSupabase, shouldUpdateGlobalStateOnCha
20
20
  switch (data.operation) {
21
21
  case "INSERT":
22
22
  operation = EnumRealtimeOperation.INSERT;
23
- if (data.record && shouldUpdateGlobalStateOnChanges) {
24
- console.log("[Tags] New tag received:", data.record);
25
- dispatch(addTag(data.record));
23
+ if (data.record && invalidateRTKQuery) {
24
+ console.log("[Tags] New tag received, invalidating RTK Query cache:", data.record);
25
+ // Tags are part of events, so invalidate events queries
26
+ dispatch(scoutApi.util.invalidateTags(["Event"]));
26
27
  }
27
28
  break;
28
29
  case "UPDATE":
29
30
  operation = EnumRealtimeOperation.UPDATE;
30
- if (data.record && shouldUpdateGlobalStateOnChanges) {
31
- console.log("[Tags] Tag updated:", data.record);
32
- dispatch(updateTag(data.record));
31
+ if (data.record && invalidateRTKQuery) {
32
+ console.log("[Tags] Tag updated, invalidating RTK Query cache:", data.record);
33
+ // Invalidate events queries since tags are embedded in events
34
+ dispatch(scoutApi.util.invalidateTags(["Event"]));
33
35
  }
34
36
  break;
35
37
  case "DELETE":
36
38
  operation = EnumRealtimeOperation.DELETE;
37
- if (data.old_record && shouldUpdateGlobalStateOnChanges) {
38
- console.log("[Tags] Tag deleted:", data.old_record);
39
- dispatch(deleteTag(data.old_record));
39
+ if (data.old_record && invalidateRTKQuery) {
40
+ console.log("[Tags] Tag deleted, invalidating RTK Query cache:", data.old_record);
41
+ // Invalidate events queries since tags are embedded in events
42
+ dispatch(scoutApi.util.invalidateTags(["Event"]));
40
43
  }
41
44
  break;
42
45
  default:
@@ -48,7 +51,7 @@ export function useScoutRealtimeTags(scoutSupabase, shouldUpdateGlobalStateOnCha
48
51
  };
49
52
  console.log(`[scout-core realtime] TAG ${data.operation} received:`, JSON.stringify(realtimeData));
50
53
  setLatestTagUpdate(realtimeData);
51
- }, [dispatch]);
54
+ }, [dispatch, invalidateRTKQuery]);
52
55
  // Clear latest update
53
56
  const clearLatestUpdate = useCallback(() => {
54
57
  setLatestTagUpdate(null);
package/dist/index.d.ts CHANGED
@@ -48,6 +48,7 @@ export * from "./hooks/useScoutRealtimeSessions";
48
48
  export * from "./hooks/useScoutRealtimePlans";
49
49
  export * from "./hooks/useScoutRealtimePins";
50
50
  export * from "./hooks/useScoutRefresh";
51
+ export * from "./hooks/useInfiniteQuery";
51
52
  export * from "./providers";
52
53
  export * from "./store/scout";
53
54
  export * from "./store/hooks";
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ export * from "./hooks/useScoutRealtimeSessions";
52
52
  export * from "./hooks/useScoutRealtimePlans";
53
53
  export * from "./hooks/useScoutRealtimePins";
54
54
  export * from "./hooks/useScoutRefresh";
55
+ export * from "./hooks/useInfiniteQuery";
55
56
  // Providers
56
57
  export * from "./providers";
57
58
  // Store