@adventurelabs/scout-core 1.0.53 → 1.0.55

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,7 +1,3 @@
1
- import { IEvent } from "../types/db";
2
1
  import { IWebResponseCompatible } from "../types/requests";
3
2
  import { EnumSessionsVisibility } from "../types/events";
4
- export declare function server_get_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<IEvent[]>>;
5
- export declare function server_get_more_events_by_herd(herd_id: number, offset: number, page_count: number | undefined, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<IEvent[]>>;
6
3
  export declare function server_get_total_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<number>>;
7
- export declare function server_create_event(newEvent: any): Promise<IWebResponseCompatible<boolean>>;
@@ -1,79 +1,16 @@
1
1
  "use server";
2
2
  import { newServerClient } from "../supabase/server";
3
3
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
- import { addSignedUrlsToEvents } from "./storage";
5
4
  import { EnumSessionsVisibility } from "../types/events";
6
- export async function server_get_events_by_herd(herd_id, sessions_visibility) {
7
- const supabase = await newServerClient();
8
- // fetch events and include devices
9
- // sort by timestamp
10
- let query = supabase
11
- .from("events")
12
- .select(`
13
- *,
14
- devices: devices!inner(*)
15
- `)
16
- .eq("devices.herd_id", herd_id);
17
- // Apply session filter based on sessions_visibility
18
- if (sessions_visibility === EnumSessionsVisibility.Only) {
19
- query = query.not("session_id", "is", null);
20
- }
21
- else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
22
- query = query.is("session_id", null);
23
- }
24
- const { data } = await query.order("timestamp_observation", { ascending: false });
25
- // Add signed URLs to events using the same client
26
- const eventsWithSignedUrls = data
27
- ? await addSignedUrlsToEvents(data, supabase)
28
- : [];
29
- // TODO: DETERMINE WHEN TO PASS ERROR
30
- let response = IWebResponse.success(eventsWithSignedUrls);
31
- return response.to_compatible();
32
- }
33
- export async function server_get_more_events_by_herd(herd_id, offset, page_count = 10, sessions_visibility) {
34
- const from = offset * page_count;
35
- const to = from + page_count - 1;
36
- const supabase = await newServerClient();
37
- // fetch events and include devices
38
- // sort by timestamp
39
- let query = supabase
40
- .from("events")
41
- .select(`
42
- *,
43
- devices: devices!inner(*)
44
- `)
45
- .eq("devices.herd_id", herd_id)
46
- .range(from, to);
47
- // Apply session filter based on sessions_visibility
48
- if (sessions_visibility === EnumSessionsVisibility.Only) {
49
- query = query.not("session_id", "is", null);
50
- }
51
- else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
52
- query = query.is("session_id", null);
53
- }
54
- const { data } = await query.order("timestamp_observation", { ascending: false });
55
- // Add signed URLs to events using the same client
56
- const eventsWithSignedUrls = data
57
- ? await addSignedUrlsToEvents(data, supabase)
58
- : [];
59
- // TODO: DETERMINE WHEN TO PASS ERROR
60
- let response = IWebResponse.success(eventsWithSignedUrls);
61
- return response.to_compatible();
62
- }
63
5
  // function to get total number of events for a herd
64
6
  export async function server_get_total_events_by_herd(herd_id, sessions_visibility) {
65
7
  const supabase = await newServerClient();
66
- let query = supabase
67
- .from("events")
68
- .select("id", { count: "exact", head: true })
69
- .eq("devices.herd_id", herd_id);
70
- if (sessions_visibility === EnumSessionsVisibility.Only) {
71
- query = query.not("session_id", "is", null);
72
- }
73
- else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
74
- query = query.is("session_id", null);
75
- }
76
- const { count, error } = await query;
8
+ // Convert sessions_visibility to exclude_session_events boolean
9
+ const exclude_session_events = sessions_visibility === EnumSessionsVisibility.Exclude;
10
+ const { data, error } = (await supabase.rpc("get_total_events_for_herd_with_session_filter", {
11
+ herd_id_caller: herd_id,
12
+ exclude_session_events: exclude_session_events,
13
+ }));
77
14
  if (error) {
78
15
  return {
79
16
  status: EnumWebResponse.ERROR,
@@ -82,21 +19,6 @@ export async function server_get_total_events_by_herd(herd_id, sessions_visibili
82
19
  };
83
20
  }
84
21
  else {
85
- return IWebResponse.success(count || 0).to_compatible();
86
- }
87
- }
88
- export async function server_create_event(newEvent) {
89
- const supabase = await newServerClient();
90
- // strip id field from herd object
91
- const { data, error } = await supabase.from("events").insert([newEvent]);
92
- if (error) {
93
- return {
94
- status: EnumWebResponse.ERROR,
95
- msg: error.message,
96
- data: null,
97
- };
98
- }
99
- else {
100
- return IWebResponse.success(true).to_compatible();
22
+ return IWebResponse.success(data || 0).to_compatible();
101
23
  }
102
24
  }
@@ -0,0 +1,3 @@
1
+ import { ILayer } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ export declare function server_get_layers_by_herd(herd_id: number): Promise<IWebResponseCompatible<ILayer[]>>;
@@ -0,0 +1,21 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
+ // function that fetches the layers from our db given a herd id
5
+ export async function server_get_layers_by_herd(herd_id) {
6
+ const supabase = await newServerClient();
7
+ const { data, error } = await supabase
8
+ .from("layers")
9
+ .select("*")
10
+ .eq("herd_id", herd_id);
11
+ if (error) {
12
+ return {
13
+ status: EnumWebResponse.ERROR,
14
+ msg: error.message,
15
+ data: null,
16
+ };
17
+ }
18
+ else {
19
+ return IWebResponse.success(data).to_compatible();
20
+ }
21
+ }
@@ -1,6 +1,5 @@
1
1
  import { IEventWithTags, ITag } from "../types/db";
2
2
  import { IWebResponseCompatible } from "../types/requests";
3
- export declare function test_event_loading(device_id: number): Promise<boolean>;
4
3
  export declare function server_create_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
5
4
  export declare function server_delete_tags_by_ids(tag_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
6
5
  export declare function server_update_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
@@ -3,25 +3,6 @@
3
3
  import { newServerClient } from "../supabase/server";
4
4
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
5
5
  import { addSignedUrlsToEvents, addSignedUrlToEvent } from "./storage";
6
- // Test function to verify individual event loading works
7
- export async function test_event_loading(device_id) {
8
- try {
9
- console.log(`[Event Test] Testing individual event loading for device ${device_id}`);
10
- const events_response = await server_get_events_and_tags_for_device(device_id, 1);
11
- if (events_response.status === EnumWebResponse.SUCCESS) {
12
- console.log(`[Event Test] Successfully loaded ${events_response.data?.length || 0} events for device ${device_id}`);
13
- return true;
14
- }
15
- else {
16
- console.error(`[Event Test] Failed to load events for device ${device_id}:`, events_response.msg);
17
- return false;
18
- }
19
- }
20
- catch (error) {
21
- console.error(`[Event Test] Failed to load events for device ${device_id}:`, error);
22
- return false;
23
- }
24
- }
25
6
  export async function server_create_tags(tags) {
26
7
  const supabase = await newServerClient();
27
8
  // remove id key from tags
@@ -94,29 +75,6 @@ export async function server_update_tags(tags) {
94
75
  }
95
76
  return IWebResponse.success(updatedTags).to_compatible();
96
77
  }
97
- // export async function server_get_events_with_tags_by_herd(
98
- // herd_id: number
99
- // ): Promise<IWebResponseCompatible<IEventWithTags[]>> {
100
- // const supabase = await newServerClient();
101
- // const { data, error } = await supabase
102
- // .from("events")
103
- // .select(
104
- // `
105
- // *,
106
- // tags: tags (*)
107
- // `
108
- // )
109
- // .eq("devices.herd_id", herd_id)
110
- // .order("timestamp_observation", { ascending: false });
111
- // if (error) {
112
- // return {
113
- // status: EnumWebResponse.ERROR,
114
- // msg: error.message,
115
- // data: [],
116
- // };
117
- // }
118
- // return IWebResponse.success(data).to_compatible();
119
- // }
120
78
  export async function server_get_more_events_with_tags_by_herd(herd_id, offset, page_count = 10) {
121
79
  const from = offset * page_count;
122
80
  const to = from + page_count - 1;
@@ -1,18 +1,3 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
- declare enum ConnectionState {
4
- DISCONNECTED = "disconnected",
5
- CONNECTING = "connecting",
6
- CONNECTED = "connected",
7
- ERROR = "error"
8
- }
9
- /**
10
- * Hook for listening to real-time database changes
11
- */
12
- export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Database>): {
13
- connectionState: ConnectionState;
14
- lastError: string | null;
15
- isConnected: boolean;
16
- isConnecting: boolean;
17
- };
18
- export {};
3
+ export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Database>): void;
@@ -1,170 +1,127 @@
1
1
  "use client";
2
2
  import { useAppDispatch } from "../store/hooks";
3
- import { useEffect, useRef, useState } from "react";
4
- import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
5
- // Connection state enum
6
- var ConnectionState;
7
- (function (ConnectionState) {
8
- ConnectionState["DISCONNECTED"] = "disconnected";
9
- ConnectionState["CONNECTING"] = "connecting";
10
- ConnectionState["CONNECTED"] = "connected";
11
- ConnectionState["ERROR"] = "error";
12
- })(ConnectionState || (ConnectionState = {}));
13
- /**
14
- * Hook for listening to real-time database changes
15
- */
3
+ import { useEffect, useRef } from "react";
4
+ import { addDevice, addPlan, addTag, addSessionToStore, deleteDevice, deletePlan, deleteSessionFromStore, deleteTag, updateDevice, updatePlan, updateSessionInStore, updateTag, } from "../store/scout";
16
5
  export function useScoutDbListener(scoutSupabase) {
6
+ const supabase = useRef(null);
17
7
  const channels = useRef([]);
18
8
  const dispatch = useAppDispatch();
19
- const [connectionState, setConnectionState] = useState(ConnectionState.DISCONNECTED);
20
- const [lastError, setLastError] = useState(null);
21
- // Clean up all channels
22
- const cleanupChannels = () => {
23
- channels.current.forEach((channel) => {
24
- if (channel && scoutSupabase) {
25
- try {
26
- scoutSupabase.removeChannel(channel);
27
- }
28
- catch (error) {
29
- console.warn("[DB Listener] Error removing channel:", error);
30
- }
31
- }
32
- });
33
- channels.current = [];
34
- };
35
- // Create event handlers
36
- const handlers = {
37
- tags: {
38
- INSERT: (payload) => {
39
- if (payload.new)
40
- dispatch(addTag(payload.new));
41
- },
42
- UPDATE: (payload) => {
43
- if (payload.new)
44
- dispatch(updateTag(payload.new));
45
- },
46
- DELETE: (payload) => {
47
- if (payload.old)
48
- dispatch(deleteTag(payload.old));
49
- },
50
- },
51
- devices: {
52
- INSERT: (payload) => {
53
- if (payload.new)
54
- dispatch(addDevice(payload.new));
55
- },
56
- UPDATE: (payload) => {
57
- if (payload.new)
58
- dispatch(updateDevice(payload.new));
59
- },
60
- DELETE: (payload) => {
61
- if (payload.old)
62
- dispatch(deleteDevice(payload.old));
63
- },
64
- },
65
- plans: {
66
- INSERT: (payload) => {
67
- if (payload.new)
68
- dispatch(addPlan(payload.new));
69
- },
70
- UPDATE: (payload) => {
71
- if (payload.new)
72
- dispatch(updatePlan(payload.new));
73
- },
74
- DELETE: (payload) => {
75
- if (payload.old)
76
- dispatch(deletePlan(payload.old));
77
- },
78
- },
79
- sessions: {
80
- INSERT: (payload) => console.log("[DB Listener] Session INSERT:", payload),
81
- UPDATE: (payload) => console.log("[DB Listener] Session UPDATE:", payload),
82
- DELETE: (payload) => console.log("[DB Listener] Session DELETE:", payload),
83
- },
84
- connectivity: {
85
- INSERT: (payload) => console.log("[DB Listener] Connectivity INSERT:", payload),
86
- UPDATE: (payload) => console.log("[DB Listener] Connectivity UPDATE:", payload),
87
- DELETE: (payload) => console.log("[DB Listener] Connectivity DELETE:", payload),
88
- },
89
- };
90
- // Set up channels
91
- const setupChannels = async () => {
92
- if (!scoutSupabase)
93
- return false;
94
- cleanupChannels();
95
- const tables = Object.keys(handlers);
96
- let successCount = 0;
97
- const totalChannels = tables.length;
98
- for (const tableName of tables) {
99
- try {
100
- const channelName = `scout_broadcast_${tableName}_${Date.now()}`;
101
- const channel = scoutSupabase.channel(channelName, {
102
- config: { private: false },
103
- });
104
- // Set up event handlers
105
- const tableHandler = handlers[tableName];
106
- Object.entries(tableHandler).forEach(([event, handler]) => {
107
- channel.on("broadcast", { event }, handler);
108
- });
109
- // Subscribe to the channel
110
- channel.subscribe((status) => {
111
- if (status === "SUBSCRIBED") {
112
- successCount++;
113
- if (successCount === totalChannels) {
114
- setConnectionState(ConnectionState.CONNECTED);
115
- setLastError(null);
116
- }
117
- }
118
- else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
119
- setLastError(`Channel subscription failed: ${status}`);
120
- }
121
- });
122
- channels.current.push(channel);
123
- }
124
- catch (error) {
125
- console.error(`[DB Listener] Failed to set up ${tableName} channel:`, error);
126
- }
127
- }
128
- return successCount > 0;
129
- };
130
- // Initialize connection
131
- const initializeConnection = async () => {
132
- if (!scoutSupabase)
9
+ function handleTagInserts(payload) {
10
+ console.log("[DB Listener] Tag INSERT received:", payload.new);
11
+ dispatch(addTag(payload.new));
12
+ }
13
+ function handleTagDeletes(payload) {
14
+ console.log("[DB Listener] Tag DELETE received:", payload.old);
15
+ if (!payload.old || !payload.old.id) {
16
+ console.error("[DB Listener] Tag DELETE - Invalid payload, missing tag data");
133
17
  return;
134
- setConnectionState(ConnectionState.CONNECTING);
135
- try {
136
- // Test database connection
137
- const { error } = await scoutSupabase.from("tags").select("id").limit(1);
138
- if (error) {
139
- throw new Error("Database connection test failed");
140
- }
141
- // Set up channels
142
- const success = await setupChannels();
143
- if (!success) {
144
- throw new Error("Channel setup failed");
145
- }
146
18
  }
147
- catch (error) {
148
- setLastError(error instanceof Error ? error.message : "Unknown error");
149
- setConnectionState(ConnectionState.ERROR);
19
+ dispatch(deleteTag(payload.old));
20
+ }
21
+ function handleTagUpdates(payload) {
22
+ console.log("[DB Listener] Tag UPDATE received:", payload.new);
23
+ dispatch(updateTag(payload.new));
24
+ }
25
+ function handleDeviceInserts(payload) {
26
+ console.log("[DB Listener] Device INSERT received:", payload.new);
27
+ dispatch(addDevice(payload.new));
28
+ }
29
+ function handleDeviceDeletes(payload) {
30
+ console.log("[DB Listener] Device DELETE received:", payload.old);
31
+ dispatch(deleteDevice(payload.old));
32
+ }
33
+ function handleDeviceUpdates(payload) {
34
+ console.log("[DB Listener] Device UPDATE received:", payload.new);
35
+ dispatch(updateDevice(payload.new));
36
+ }
37
+ function handlePlanInserts(payload) {
38
+ console.log("[DB Listener] Plan INSERT received:", payload.new);
39
+ dispatch(addPlan(payload.new));
40
+ }
41
+ function handlePlanDeletes(payload) {
42
+ console.log("[DB Listener] Plan DELETE received:", payload.old);
43
+ dispatch(deletePlan(payload.old));
44
+ }
45
+ function handlePlanUpdates(payload) {
46
+ console.log("[DB Listener] Plan UPDATE received:", payload.new);
47
+ dispatch(updatePlan(payload.new));
48
+ }
49
+ function handleSessionInserts(payload) {
50
+ console.log("[DB Listener] Session INSERT received:", payload.new);
51
+ dispatch(addSessionToStore(payload.new));
52
+ }
53
+ function handleSessionDeletes(payload) {
54
+ console.log("[DB Listener] Session DELETE received:", payload.old);
55
+ if (!payload.old || !payload.old.id) {
56
+ console.error("[DB Listener] Session DELETE - Invalid payload, missing session data");
57
+ return;
150
58
  }
151
- };
152
- // Main effect
59
+ dispatch(deleteSessionFromStore(payload.old));
60
+ }
61
+ function handleSessionUpdates(payload) {
62
+ console.log("[DB Listener] Session UPDATE received:", payload.new);
63
+ dispatch(updateSessionInStore(payload.new));
64
+ }
65
+ function handleConnectivityInserts(payload) {
66
+ console.log("[DB Listener] Connectivity INSERT received:", payload.new);
67
+ }
68
+ function handleConnectivityDeletes(payload) {
69
+ console.log("[DB Listener] Connectivity DELETE received:", payload.old);
70
+ }
71
+ function handleConnectivityUpdates(payload) {
72
+ console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
73
+ }
153
74
  useEffect(() => {
154
75
  if (!scoutSupabase) {
155
- setConnectionState(ConnectionState.ERROR);
156
- setLastError("No Supabase client available");
76
+ console.error("[DB Listener] No Supabase client available");
157
77
  return;
158
78
  }
159
- initializeConnection();
79
+ supabase.current = scoutSupabase;
80
+ // Create a single channel for all operations
81
+ const channelName = `scout_realtime_${Date.now()}`;
82
+ const mainChannel = scoutSupabase.channel(channelName);
83
+ // Subscribe to all events
84
+ mainChannel
85
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
86
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
87
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
88
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
89
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
90
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
91
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
92
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
93
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
94
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleConnectivityInserts)
95
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleConnectivityDeletes)
96
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleConnectivityUpdates)
97
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleSessionInserts)
98
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleSessionDeletes)
99
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleSessionUpdates)
100
+ .subscribe((status) => {
101
+ console.log("[DB Listener] Subscription status:", status);
102
+ if (status === "SUBSCRIBED") {
103
+ console.log("[DB Listener] ✅ Successfully subscribed to real-time updates");
104
+ }
105
+ else if (status === "CHANNEL_ERROR") {
106
+ console.error("[DB Listener] ❌ Channel error occurred");
107
+ }
108
+ else if (status === "TIMED_OUT") {
109
+ console.error("[DB Listener] ⏰ Subscription timed out");
110
+ }
111
+ else if (status === "CLOSED") {
112
+ console.log("[DB Listener] 🔒 Channel closed");
113
+ }
114
+ });
115
+ channels.current.push(mainChannel);
116
+ // Cleanup function
160
117
  return () => {
161
- cleanupChannels();
118
+ console.log("[DB Listener] 🧹 Cleaning up channels");
119
+ channels.current.forEach((channel) => {
120
+ if (channel) {
121
+ scoutSupabase.removeChannel(channel);
122
+ }
123
+ });
124
+ channels.current = [];
162
125
  };
163
- }, [scoutSupabase]);
164
- return {
165
- connectionState,
166
- lastError,
167
- isConnected: connectionState === ConnectionState.CONNECTED,
168
- isConnecting: connectionState === ConnectionState.CONNECTING,
169
- };
126
+ }, [scoutSupabase, dispatch]);
170
127
  }
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from "./helpers/gps";
19
19
  export * from "./helpers/herds";
20
20
  export * from "./helpers/location";
21
21
  export * from "./helpers/plans";
22
+ export * from "./helpers/layers";
22
23
  export * from "./helpers/sessions";
23
24
  export * from "./helpers/tags";
24
25
  export * from "./helpers/time";
@@ -36,7 +37,6 @@ export * from "./store/hooks";
36
37
  export * from "./supabase/middleware";
37
38
  export * from "./supabase/server";
38
39
  export * from "./api_keys/actions";
39
- export type { Database } from "./types/supabase";
40
40
  export type { HerdModule, IHerdModule } from "./types/herd_module";
41
- export type { IDevice, IEvent, IUser, IHerd, IEventWithTags, IZoneWithActions, IUserAndRole, IApiKeyScout, } from "./types/db";
41
+ export type { IDevice, IEvent, IUser, IHerd, IEventWithTags, IZoneWithActions, IUserAndRole, IApiKeyScout, ILayer, } from "./types/db";
42
42
  export { EnumSessionsVisibility } from "./types/events";
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export * from "./helpers/gps";
22
22
  export * from "./helpers/herds";
23
23
  export * from "./helpers/location";
24
24
  export * from "./helpers/plans";
25
+ export * from "./helpers/layers";
25
26
  export * from "./helpers/sessions";
26
27
  export * from "./helpers/tags";
27
28
  export * from "./helpers/time";