@adventurelabs/scout-core 1.0.37 → 1.0.39

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.
@@ -4,7 +4,6 @@ declare enum ConnectionState {
4
4
  DISCONNECTED = "disconnected",
5
5
  CONNECTING = "connecting",
6
6
  CONNECTED = "connected",
7
- RECONNECTING = "reconnecting",
8
7
  ERROR = "error"
9
8
  }
10
9
  /**
@@ -28,3 +27,39 @@ export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Databas
28
27
  isConnecting: boolean;
29
28
  };
30
29
  export {};
30
+ /**
31
+ * Return type for useScoutDbListener hook
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function MyComponent() {
36
+ * const {
37
+ * isConnected,
38
+ * isConnecting,
39
+ * lastError,
40
+ * retryCount,
41
+ * reconnect
42
+ * } = useConnectionStatus();
43
+ *
44
+ * if (isConnecting) {
45
+ * return <div>Connecting to database...</div>;
46
+ * }
47
+ *
48
+ * if (lastError) {
49
+ * return (
50
+ * <div>
51
+ * <p>Connection error: {lastError}</p>
52
+ * <p>Retry attempts: {retryCount}</p>
53
+ * <button onClick={reconnect}>Reconnect</button>
54
+ * </div>
55
+ * );
56
+ * }
57
+ *
58
+ * if (!isConnected) {
59
+ * return <div>Disconnected from database</div>;
60
+ * }
61
+ *
62
+ * return <div>Connected to database</div>;
63
+ * }
64
+ * ```
65
+ */
@@ -8,7 +8,6 @@ var ConnectionState;
8
8
  ConnectionState["DISCONNECTED"] = "disconnected";
9
9
  ConnectionState["CONNECTING"] = "connecting";
10
10
  ConnectionState["CONNECTED"] = "connected";
11
- ConnectionState["RECONNECTING"] = "reconnecting";
12
11
  ConnectionState["ERROR"] = "error";
13
12
  })(ConnectionState || (ConnectionState = {}));
14
13
  // Reconnection configuration
@@ -107,118 +106,46 @@ export function useScoutDbListener(scoutSupabase) {
107
106
  return false;
108
107
  }
109
108
  }, []);
110
- // Event handlers
111
- const handleTagInserts = useCallback((payload) => {
112
- console.log("[DB Listener] Tag INSERT received:", payload);
113
- if (!payload.new) {
114
- console.error("[DB Listener] Tag INSERT - Invalid payload, missing new data");
115
- return;
116
- }
117
- dispatch(addTag(payload.new));
118
- }, [dispatch]);
119
- const handleTagDeletes = useCallback((payload) => {
120
- console.log("[DB Listener] Tag DELETE received:", payload);
121
- if (!payload.old || !payload.old.id) {
122
- console.error("[DB Listener] Tag DELETE - Invalid payload, missing tag data");
123
- return;
124
- }
125
- dispatch(deleteTag(payload.old));
126
- }, [dispatch]);
127
- const handleTagUpdates = useCallback((payload) => {
128
- console.log("[DB Listener] Tag UPDATE received:", payload);
129
- if (!payload.new) {
130
- console.error("[DB Listener] Tag UPDATE - Invalid payload, missing new data");
131
- return;
132
- }
133
- dispatch(updateTag(payload.new));
134
- }, [dispatch]);
135
- const handleDeviceInserts = useCallback((payload) => {
136
- console.log("[DB Listener] Device INSERT received:", payload);
137
- if (!payload.new) {
138
- console.error("[DB Listener] Device INSERT - Invalid payload, missing new data");
139
- return;
140
- }
141
- dispatch(addDevice(payload.new));
142
- }, [dispatch]);
143
- const handleDeviceDeletes = useCallback((payload) => {
144
- console.log("[DB Listener] Device DELETE received:", payload);
145
- if (!payload.old) {
146
- console.error("[DB Listener] Device DELETE - Invalid payload, missing old data");
147
- return;
148
- }
149
- dispatch(deleteDevice(payload.old));
150
- }, [dispatch]);
151
- const handleDeviceUpdates = useCallback((payload) => {
152
- console.log("[DB Listener] Device UPDATE received:", payload);
153
- if (!payload.new) {
154
- console.error("[DB Listener] Device UPDATE - Invalid payload, missing new data");
155
- return;
156
- }
157
- dispatch(updateDevice(payload.new));
158
- }, [dispatch]);
159
- const handlePlanInserts = useCallback((payload) => {
160
- console.log("[DB Listener] Plan INSERT received:", payload);
161
- if (!payload.new) {
162
- console.error("[DB Listener] Plan INSERT - Invalid payload, missing new data");
163
- return;
164
- }
165
- dispatch(addPlan(payload.new));
166
- }, [dispatch]);
167
- const handlePlanDeletes = useCallback((payload) => {
168
- console.log("[DB Listener] Plan DELETE received:", payload);
169
- if (!payload.old) {
170
- console.error("[DB Listener] Plan DELETE - Invalid payload, missing old data");
171
- return;
172
- }
173
- dispatch(deletePlan(payload.old));
174
- }, [dispatch]);
175
- const handlePlanUpdates = useCallback((payload) => {
176
- console.log("[DB Listener] Plan UPDATE received:", payload);
177
- if (!payload.new) {
178
- console.error("[DB Listener] Plan UPDATE - Invalid payload, missing new data");
179
- return;
180
- }
181
- dispatch(updatePlan(payload.new));
182
- }, [dispatch]);
183
- const handleSessionInserts = useCallback((payload) => {
184
- console.log("[DB Listener] Session INSERT received:", payload);
185
- if (!payload.new) {
186
- console.error("[DB Listener] Session INSERT - Invalid payload, missing new data");
187
- return;
188
- }
189
- dispatch(addSessionToStore(payload.new));
190
- }, [dispatch]);
191
- const handleSessionDeletes = useCallback((payload) => {
192
- console.log("[DB Listener] Session DELETE received:", payload);
193
- if (!payload.old) {
194
- console.error("[DB Listener] Session DELETE - Invalid payload, missing old data");
195
- return;
196
- }
197
- dispatch(deleteSessionFromStore(payload.old));
198
- }, [dispatch]);
199
- const handleSessionUpdates = useCallback((payload) => {
200
- console.log("[DB Listener] Session UPDATE received:", payload);
201
- if (!payload.new) {
202
- console.error("[DB Listener] Session UPDATE - Invalid payload, missing new data");
203
- return;
204
- }
205
- dispatch(updateSessionInStore(payload.new));
206
- }, [dispatch]);
207
- const handleConnectivityInserts = useCallback((payload) => {
208
- console.log("[DB Listener] Connectivity INSERT received:", payload);
209
- // For now, we'll just log connectivity changes since they're related to sessions
210
- // In the future, we might want to update session connectivity data
211
- }, []);
212
- const handleConnectivityDeletes = useCallback((payload) => {
213
- console.log("[DB Listener] Connectivity DELETE received:", payload);
214
- // For now, we'll just log connectivity changes since they're related to sessions
215
- // In the future, we might want to update session connectivity data
216
- }, []);
217
- const handleConnectivityUpdates = useCallback((payload) => {
218
- console.log("[DB Listener] Connectivity UPDATE received:", payload);
219
- // For now, we'll just log connectivity changes since they're related to sessions
220
- // In the future, we might want to update session connectivity data
109
+ // Generic event handler factory
110
+ const createEventHandler = useCallback((action, dataKey, entityName) => {
111
+ return (payload) => {
112
+ console.log(`[DB Listener] ${entityName} ${payload.event} received:`, payload);
113
+ const data = payload[dataKey];
114
+ if (!data) {
115
+ console.error(`[DB Listener] ${entityName} ${payload.event} - Invalid payload, missing ${dataKey} data`);
116
+ return;
117
+ }
118
+ action(data);
119
+ };
221
120
  }, []);
121
+ // Create event handlers using the factory
122
+ const handlers = useCallback(() => ({
123
+ tags: {
124
+ INSERT: createEventHandler(dispatch.bind(null, addTag), "new", "Tag"),
125
+ UPDATE: createEventHandler(dispatch.bind(null, updateTag), "new", "Tag"),
126
+ DELETE: createEventHandler(dispatch.bind(null, deleteTag), "old", "Tag"),
127
+ },
128
+ devices: {
129
+ INSERT: createEventHandler(dispatch.bind(null, addDevice), "new", "Device"),
130
+ UPDATE: createEventHandler(dispatch.bind(null, updateDevice), "new", "Device"),
131
+ DELETE: createEventHandler(dispatch.bind(null, deleteDevice), "old", "Device"),
132
+ },
133
+ plans: {
134
+ INSERT: createEventHandler(dispatch.bind(null, addPlan), "new", "Plan"),
135
+ UPDATE: createEventHandler(dispatch.bind(null, updatePlan), "new", "Plan"),
136
+ DELETE: createEventHandler(dispatch.bind(null, deletePlan), "old", "Plan"),
137
+ },
138
+ sessions: {
139
+ INSERT: createEventHandler(dispatch.bind(null, addSessionToStore), "new", "Session"),
140
+ UPDATE: createEventHandler(dispatch.bind(null, updateSessionInStore), "new", "Session"),
141
+ DELETE: createEventHandler(dispatch.bind(null, deleteSessionFromStore), "old", "Session"),
142
+ },
143
+ connectivity: {
144
+ INSERT: (payload) => console.log("[DB Listener] Connectivity INSERT received:", payload),
145
+ UPDATE: (payload) => console.log("[DB Listener] Connectivity UPDATE received:", payload),
146
+ DELETE: (payload) => console.log("[DB Listener] Connectivity DELETE received:", payload),
147
+ },
148
+ }), [createEventHandler, dispatch]);
222
149
  // Create a channel with proper error handling
223
150
  const createChannel = useCallback((tableName) => {
224
151
  if (!supabase.current)
@@ -233,11 +160,8 @@ export function useScoutDbListener(scoutSupabase) {
233
160
  channel
234
161
  .on("system", { event: "disconnect" }, () => {
235
162
  console.log(`[DB Listener] 🔌 ${tableName} channel disconnected`);
236
- if (connectionState === ConnectionState.CONNECTED) {
237
- setConnectionState(ConnectionState.DISCONNECTED);
238
- setLastError("Channel disconnected");
239
- scheduleReconnection();
240
- }
163
+ setConnectionState(ConnectionState.DISCONNECTED);
164
+ setLastError("Channel disconnected");
241
165
  })
242
166
  .on("system", { event: "reconnect" }, () => {
243
167
  console.log(`[DB Listener] 🔗 ${tableName} channel reconnected`);
@@ -252,68 +176,29 @@ export function useScoutDbListener(scoutSupabase) {
252
176
  console.error(`[DB Listener] Failed to create ${tableName} channel:`, error);
253
177
  return null;
254
178
  }
255
- }, [connectionState]);
179
+ }, []);
256
180
  // Set up all channels
257
181
  const setupChannels = useCallback(async () => {
258
182
  if (!supabase.current)
259
183
  return false;
260
184
  cleanupChannels();
261
- const channelConfigs = [
262
- {
263
- name: "plans",
264
- handlers: {
265
- INSERT: handlePlanInserts,
266
- UPDATE: handlePlanUpdates,
267
- DELETE: handlePlanDeletes,
268
- },
269
- },
270
- {
271
- name: "devices",
272
- handlers: {
273
- INSERT: handleDeviceInserts,
274
- UPDATE: handleDeviceUpdates,
275
- DELETE: handleDeviceDeletes,
276
- },
277
- },
278
- {
279
- name: "tags",
280
- handlers: {
281
- INSERT: handleTagInserts,
282
- UPDATE: handleTagUpdates,
283
- DELETE: handleTagDeletes,
284
- },
285
- },
286
- {
287
- name: "sessions",
288
- handlers: {
289
- INSERT: handleSessionInserts,
290
- UPDATE: handleSessionUpdates,
291
- DELETE: handleSessionDeletes,
292
- },
293
- },
294
- {
295
- name: "connectivity",
296
- handlers: {
297
- INSERT: handleConnectivityInserts,
298
- UPDATE: handleConnectivityUpdates,
299
- DELETE: handleConnectivityDeletes,
300
- },
301
- },
302
- ];
185
+ const tableHandlers = handlers();
186
+ const tables = Object.keys(tableHandlers);
303
187
  let successCount = 0;
304
- const totalChannels = channelConfigs.length;
305
- for (const config of channelConfigs) {
306
- const channel = createChannel(config.name);
188
+ const totalChannels = tables.length;
189
+ for (const tableName of tables) {
190
+ const channel = createChannel(tableName);
307
191
  if (!channel)
308
192
  continue;
309
193
  try {
310
194
  // Set up event handlers
311
- Object.entries(config.handlers).forEach(([event, handler]) => {
195
+ const tableHandler = tableHandlers[tableName];
196
+ Object.entries(tableHandler).forEach(([event, handler]) => {
312
197
  channel.on("broadcast", { event }, handler);
313
198
  });
314
199
  // Subscribe to the channel
315
- const _subscription = channel.subscribe((status) => {
316
- console.log(`[DB Listener] ${config.name} channel status:`, status);
200
+ channel.subscribe((status) => {
201
+ console.log(`[DB Listener] ${tableName} channel status:`, status);
317
202
  if (status === "SUBSCRIBED") {
318
203
  successCount++;
319
204
  if (successCount === totalChannels) {
@@ -324,36 +209,18 @@ export function useScoutDbListener(scoutSupabase) {
324
209
  }
325
210
  }
326
211
  else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
327
- console.error(`[DB Listener] ${config.name} channel failed to subscribe:`, status);
212
+ console.error(`[DB Listener] ${tableName} channel failed to subscribe:`, status);
328
213
  setLastError(`Channel subscription failed: ${status}`);
329
214
  }
330
215
  });
331
216
  channels.current.push(channel);
332
217
  }
333
218
  catch (error) {
334
- console.error(`[DB Listener] Failed to set up ${config.name} channel:`, error);
219
+ console.error(`[DB Listener] Failed to set up ${tableName} channel:`, error);
335
220
  }
336
221
  }
337
222
  return successCount > 0;
338
- }, [
339
- cleanupChannels,
340
- createChannel,
341
- handlePlanInserts,
342
- handlePlanUpdates,
343
- handlePlanDeletes,
344
- handleDeviceInserts,
345
- handleDeviceUpdates,
346
- handleDeviceDeletes,
347
- handleTagInserts,
348
- handleTagUpdates,
349
- handleTagDeletes,
350
- handleSessionInserts,
351
- handleSessionUpdates,
352
- handleSessionDeletes,
353
- handleConnectivityInserts,
354
- handleConnectivityUpdates,
355
- handleConnectivityDeletes,
356
- ]);
223
+ }, [cleanupChannels, createChannel, handlers]);
357
224
  // Schedule reconnection with exponential backoff
358
225
  const scheduleReconnection = useCallback(() => {
359
226
  if (isDestroyedRef.current ||
@@ -455,7 +322,6 @@ export function useScoutDbListener(scoutSupabase) {
455
322
  retryCount,
456
323
  reconnect,
457
324
  isConnected: connectionState === ConnectionState.CONNECTED,
458
- isConnecting: connectionState === ConnectionState.CONNECTING ||
459
- connectionState === ConnectionState.RECONNECTING,
325
+ isConnecting: connectionState === ConnectionState.CONNECTING,
460
326
  };
461
327
  }
@@ -630,25 +630,11 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
630
630
  };
631
631
  Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
632
632
  };
633
- get_device_from_api_key: {
633
+ get_device_by_api_key: {
634
634
  Args: {
635
635
  device_api_key: string;
636
636
  };
637
- Returns: {
638
- altitude: number | null;
639
- created_by: string;
640
- description: string;
641
- device_type: Database["public"]["Enums"]["device_type"];
642
- domain_name: string | null;
643
- heading: number | null;
644
- herd_id: number;
645
- id: number;
646
- inserted_at: string;
647
- location: unknown | null;
648
- name: string;
649
- video_publisher_token: string | null;
650
- video_subscriber_token: string | null;
651
- };
637
+ Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
652
638
  };
653
639
  get_device_id_from_key: {
654
640
  Args: {
@@ -33,14 +33,17 @@ export function ScoutRefreshProvider({ children }) {
33
33
  console.log("[ScoutRefreshProvider] Created Supabase client");
34
34
  }
35
35
  // Use the enhanced DB listener with connection status
36
- const connectionStatus = useScoutDbListener(supabaseRef.current);
36
+ useScoutDbListener(supabaseRef.current);
37
37
  useScoutRefresh();
38
- // Log connection status changes for debugging
39
- if (connectionStatus.lastError) {
40
- console.warn("[ScoutRefreshProvider] DB Listener error:", connectionStatus.lastError);
41
- }
42
- if (connectionStatus.isConnected) {
43
- console.log("[ScoutRefreshProvider] ✅ DB Listener connected");
44
- }
45
- return (_jsx(SupabaseContext.Provider, { value: supabaseRef.current, children: _jsx(ConnectionStatusContext.Provider, { value: connectionStatus, children: children }) }));
38
+ // // Log connection status changes for debugging
39
+ // if (connectionStatus.lastError) {
40
+ // console.warn(
41
+ // "[ScoutRefreshProvider] DB Listener error:",
42
+ // connectionStatus.lastError
43
+ // );
44
+ // }
45
+ // if (connectionStatus.isConnected) {
46
+ // console.log("[ScoutRefreshProvider] ✅ DB Listener connected");
47
+ // }
48
+ return (_jsx(SupabaseContext.Provider, { value: supabaseRef.current, children: children }));
46
49
  }
@@ -68,7 +68,10 @@ export const scoutSlice = createSlice({
68
68
  addTag(state, action) {
69
69
  for (const herd_module of state.herd_modules) {
70
70
  for (const event of herd_module.events) {
71
- if (event.id === action.payload.event_id && event.tags) {
71
+ if (event.id === action.payload.event_id) {
72
+ if (event.tags == undefined || event.tags == null) {
73
+ event.tags = [];
74
+ }
72
75
  event.tags.push(action.payload);
73
76
  return;
74
77
  }
@@ -621,25 +621,11 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
621
621
  };
622
622
  Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
623
623
  };
624
- get_device_from_api_key: {
624
+ get_device_by_api_key: {
625
625
  Args: {
626
626
  device_api_key: string;
627
627
  };
628
- Returns: {
629
- altitude: number | null;
630
- created_by: string;
631
- description: string;
632
- device_type: Database["public"]["Enums"]["device_type"];
633
- domain_name: string | null;
634
- heading: number | null;
635
- herd_id: number;
636
- id: number;
637
- inserted_at: string;
638
- location: unknown | null;
639
- name: string;
640
- video_publisher_token: string | null;
641
- video_subscriber_token: string | null;
642
- };
628
+ Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
643
629
  };
644
630
  get_device_id_from_key: {
645
631
  Args: {
@@ -688,25 +688,11 @@ export type Database = {
688
688
  };
689
689
  Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
690
690
  };
691
- get_device_from_api_key: {
691
+ get_device_by_api_key: {
692
692
  Args: {
693
693
  device_api_key: string;
694
694
  };
695
- Returns: {
696
- altitude: number | null;
697
- created_by: string;
698
- description: string;
699
- device_type: Database["public"]["Enums"]["device_type"];
700
- domain_name: string | null;
701
- heading: number | null;
702
- herd_id: number;
703
- id: number;
704
- inserted_at: string;
705
- location: unknown | null;
706
- name: string;
707
- video_publisher_token: string | null;
708
- video_subscriber_token: string | null;
709
- };
695
+ Returns: Database["public"]["CompositeTypes"]["device_pretty_location"];
710
696
  };
711
697
  get_device_id_from_key: {
712
698
  Args: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
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",