@adventurelabs/scout-core 1.0.80 → 1.0.83

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.
@@ -47,6 +47,8 @@ export declare class ScoutCache {
47
47
  reason: string;
48
48
  }>;
49
49
  preloadCache(loadFunction: () => Promise<IHerdModule[]>, ttlMs?: number): Promise<void>;
50
+ getProvidersForHerd(herdId: string): Promise<CacheResult<any[]>>;
51
+ setProvidersForHerd(herdId: string, providers: any[], ttlMs?: number): Promise<void>;
50
52
  getDefaultTtl(): number;
51
53
  }
52
54
  export declare const scoutCache: ScoutCache;
@@ -2,6 +2,7 @@ const DB_NAME = "ScoutCache";
2
2
  const DB_VERSION = 1;
3
3
  const HERD_MODULES_STORE = "herd_modules";
4
4
  const CACHE_METADATA_STORE = "cache_metadata";
5
+ const PROVIDERS_STORE = "providers";
5
6
  // Default TTL: 24 hours (1 day)
6
7
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
7
8
  export class ScoutCache {
@@ -46,6 +47,15 @@ export class ScoutCache {
46
47
  keyPath: "key",
47
48
  });
48
49
  }
50
+ // Create providers store
51
+ if (!db.objectStoreNames.contains(PROVIDERS_STORE)) {
52
+ const providersStore = db.createObjectStore(PROVIDERS_STORE, {
53
+ keyPath: "herdId",
54
+ });
55
+ providersStore.createIndex("timestamp", "timestamp", {
56
+ unique: false,
57
+ });
58
+ }
49
59
  console.log("[ScoutCache] Database schema upgraded");
50
60
  };
51
61
  });
@@ -55,15 +65,16 @@ export class ScoutCache {
55
65
  await this.init();
56
66
  if (!this.db)
57
67
  throw new Error("Database not initialized");
58
- const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
68
+ const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE, PROVIDERS_STORE], "readwrite");
59
69
  return new Promise((resolve, reject) => {
60
70
  transaction.onerror = () => reject(transaction.error);
61
71
  transaction.oncomplete = () => resolve();
62
72
  const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
63
73
  const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
74
+ const providersStore = transaction.objectStore(PROVIDERS_STORE);
64
75
  const timestamp = Date.now();
65
76
  const version = "1.0.0";
66
- // Store each herd module
77
+ // Store each herd module and its providers
67
78
  herdModules.forEach((herdModule) => {
68
79
  const cacheEntry = {
69
80
  herdId: herdModule.herd.id.toString(),
@@ -71,6 +82,15 @@ export class ScoutCache {
71
82
  timestamp,
72
83
  };
73
84
  herdModulesStore.put(cacheEntry);
85
+ // Store providers separately for easier querying
86
+ if (herdModule.providers && herdModule.providers.length > 0) {
87
+ const providersCacheEntry = {
88
+ herdId: herdModule.herd.id.toString(),
89
+ data: herdModule.providers,
90
+ timestamp,
91
+ };
92
+ providersStore.put(providersCacheEntry);
93
+ }
74
94
  });
75
95
  // Store cache metadata
76
96
  const metadata = {
@@ -82,6 +102,16 @@ export class ScoutCache {
82
102
  lastModified: timestamp,
83
103
  };
84
104
  metadataStore.put(metadata);
105
+ // Store providers metadata
106
+ const providersMetadata = {
107
+ key: "providers",
108
+ timestamp,
109
+ ttl: ttlMs,
110
+ version,
111
+ etag,
112
+ lastModified: timestamp,
113
+ };
114
+ metadataStore.put(providersMetadata);
85
115
  });
86
116
  }
87
117
  async getHerdModules() {
@@ -134,14 +164,17 @@ export class ScoutCache {
134
164
  await this.init();
135
165
  if (!this.db)
136
166
  throw new Error("Database not initialized");
137
- const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE], "readwrite");
167
+ const transaction = this.db.transaction([HERD_MODULES_STORE, CACHE_METADATA_STORE, PROVIDERS_STORE], "readwrite");
138
168
  return new Promise((resolve, reject) => {
139
169
  transaction.onerror = () => reject(transaction.error);
140
170
  transaction.oncomplete = () => resolve();
141
171
  const herdModulesStore = transaction.objectStore(HERD_MODULES_STORE);
142
172
  const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
173
+ const providersStore = transaction.objectStore(PROVIDERS_STORE);
143
174
  herdModulesStore.clear();
175
+ providersStore.clear();
144
176
  metadataStore.delete("herd_modules");
177
+ metadataStore.delete("providers");
145
178
  });
146
179
  }
147
180
  async invalidateHerdModules() {
@@ -154,6 +187,7 @@ export class ScoutCache {
154
187
  transaction.oncomplete = () => resolve();
155
188
  const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
156
189
  metadataStore.delete("herd_modules");
190
+ metadataStore.delete("providers");
157
191
  });
158
192
  }
159
193
  async getCacheStats() {
@@ -214,6 +248,81 @@ export class ScoutCache {
214
248
  console.warn("[ScoutCache] Background preload failed:", error);
215
249
  }
216
250
  }
251
+ // Method to get providers for a specific herd from cache
252
+ async getProvidersForHerd(herdId) {
253
+ await this.init();
254
+ if (!this.db)
255
+ throw new Error("Database not initialized");
256
+ const transaction = this.db.transaction([PROVIDERS_STORE, CACHE_METADATA_STORE], "readonly");
257
+ return new Promise((resolve, reject) => {
258
+ transaction.onerror = () => reject(transaction.error);
259
+ const providersStore = transaction.objectStore(PROVIDERS_STORE);
260
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
261
+ // Get metadata first
262
+ const metadataRequest = metadataStore.get("providers");
263
+ metadataRequest.onsuccess = () => {
264
+ const metadata = metadataRequest.result;
265
+ const now = Date.now();
266
+ if (!metadata) {
267
+ this.stats.misses++;
268
+ resolve({ data: null, isStale: true, age: 0, metadata: null });
269
+ return;
270
+ }
271
+ const age = now - metadata.timestamp;
272
+ const isStale = age > metadata.ttl;
273
+ // Get providers for specific herd
274
+ const getRequest = providersStore.get(herdId);
275
+ getRequest.onsuccess = () => {
276
+ const cacheEntry = getRequest.result;
277
+ const providers = cacheEntry?.data || [];
278
+ // Update stats
279
+ if (providers.length > 0) {
280
+ this.stats.hits++;
281
+ }
282
+ else {
283
+ this.stats.misses++;
284
+ }
285
+ resolve({
286
+ data: providers,
287
+ isStale,
288
+ age,
289
+ metadata,
290
+ });
291
+ };
292
+ };
293
+ });
294
+ }
295
+ // Method to set providers for a specific herd
296
+ async setProvidersForHerd(herdId, providers, ttlMs = DEFAULT_TTL_MS) {
297
+ await this.init();
298
+ if (!this.db)
299
+ throw new Error("Database not initialized");
300
+ const transaction = this.db.transaction([PROVIDERS_STORE, CACHE_METADATA_STORE], "readwrite");
301
+ return new Promise((resolve, reject) => {
302
+ transaction.onerror = () => reject(transaction.error);
303
+ transaction.oncomplete = () => resolve();
304
+ const providersStore = transaction.objectStore(PROVIDERS_STORE);
305
+ const metadataStore = transaction.objectStore(CACHE_METADATA_STORE);
306
+ const timestamp = Date.now();
307
+ const version = "1.0.0";
308
+ // Store providers
309
+ const providersCacheEntry = {
310
+ herdId,
311
+ data: providers,
312
+ timestamp,
313
+ };
314
+ providersStore.put(providersCacheEntry);
315
+ // Update providers metadata
316
+ const metadata = {
317
+ key: "providers",
318
+ timestamp,
319
+ ttl: ttlMs,
320
+ version,
321
+ lastModified: timestamp,
322
+ };
323
+ metadataStore.put(metadata);
324
+ });
325
+ }
217
326
  // Get the default TTL value
218
327
  getDefaultTtl() {
219
328
  return DEFAULT_TTL_MS;
@@ -1,8 +1,9 @@
1
- import { IDevice } from "../types/db";
1
+ import { Database } from "@/types";
2
+ import { DeviceInsert, IDevice } from "../types/db";
2
3
  import { IWebResponseCompatible } from "../types/requests";
3
4
  import { SupabaseClient } from "@supabase/supabase-js";
4
- export declare function get_devices_by_herd(herd_id: number, client: SupabaseClient): Promise<IWebResponseCompatible<IDevice[]>>;
5
- export declare function get_device_by_id(device_id: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IDevice | null>>;
5
+ export declare function get_devices_by_herd(herd_id: number, client: SupabaseClient<Database>): Promise<IWebResponseCompatible<IDevice[]>>;
6
+ export declare function get_device_by_id(device_id: number, client?: SupabaseClient<Database>): Promise<IWebResponseCompatible<IDevice | null>>;
6
7
  export declare function serverUpdateDevice(updatedDevice: IDevice): Promise<IWebResponseCompatible<IDevice | null>>;
7
- export declare function serverCreateDevice(newDevice: any): Promise<IWebResponseCompatible<IDevice | null>>;
8
+ export declare function serverCreateDevice(newDevice: DeviceInsert): Promise<IWebResponseCompatible<IDevice | null>>;
8
9
  export declare function serverDeleteDeviceById(device_id: number): Promise<IWebResponseCompatible<IDevice | null>>;
@@ -60,6 +60,9 @@ export async function serverCreateDevice(newDevice) {
60
60
  const supabase = await newServerClient();
61
61
  const user = await supabase.auth.getUser();
62
62
  const userId = user?.data?.user?.id;
63
+ if (!userId) {
64
+ return IWebResponse.error("User not found").to_compatible();
65
+ }
63
66
  newDevice.created_by = userId;
64
67
  // strip id field from herd object
65
68
  const { data, error } = await supabase
@@ -0,0 +1,6 @@
1
+ import { IProvider } from "../types/db";
2
+ import { IWebResponseCompatible } from "../types/requests";
3
+ export declare function server_get_providers_by_herd(herd_id: number): Promise<IWebResponseCompatible<IProvider[]>>;
4
+ export declare function server_create_provider(provider: Omit<IProvider, "id" | "created_at">): Promise<IWebResponseCompatible<IProvider>>;
5
+ export declare function server_update_provider(provider_id: number, updates: Partial<Omit<IProvider, "id" | "created_at">>): Promise<IWebResponseCompatible<IProvider>>;
6
+ export declare function server_delete_providers_by_ids(provider_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
@@ -0,0 +1,78 @@
1
+ "use server";
2
+ import { newServerClient } from "../supabase/server";
3
+ import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
+ // function that fetches the providers from our db given a herd id
5
+ export async function server_get_providers_by_herd(herd_id) {
6
+ const supabase = await newServerClient();
7
+ const { data, error } = await supabase
8
+ .from("providers")
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
+ }
22
+ // function that creates a new provider in our db
23
+ export async function server_create_provider(provider) {
24
+ const supabase = await newServerClient();
25
+ const { data, error } = await supabase
26
+ .from("providers")
27
+ .insert(provider)
28
+ .select("*")
29
+ .single();
30
+ if (error) {
31
+ return {
32
+ status: EnumWebResponse.ERROR,
33
+ msg: error.message,
34
+ data: null,
35
+ };
36
+ }
37
+ else {
38
+ return IWebResponse.success(data).to_compatible();
39
+ }
40
+ }
41
+ // function that updates a provider in our db
42
+ export async function server_update_provider(provider_id, updates) {
43
+ const supabase = await newServerClient();
44
+ const { data, error } = await supabase
45
+ .from("providers")
46
+ .update(updates)
47
+ .eq("id", provider_id)
48
+ .select("*")
49
+ .single();
50
+ if (error) {
51
+ return {
52
+ status: EnumWebResponse.ERROR,
53
+ msg: error.message,
54
+ data: null,
55
+ };
56
+ }
57
+ else {
58
+ return IWebResponse.success(data).to_compatible();
59
+ }
60
+ }
61
+ // function that deletes providers by ids
62
+ export async function server_delete_providers_by_ids(provider_ids) {
63
+ const supabase = await newServerClient();
64
+ const { error } = await supabase
65
+ .from("providers")
66
+ .delete()
67
+ .in("id", provider_ids);
68
+ if (error) {
69
+ return {
70
+ status: EnumWebResponse.ERROR,
71
+ msg: error.message,
72
+ data: false,
73
+ };
74
+ }
75
+ else {
76
+ return IWebResponse.success(true).to_compatible();
77
+ }
78
+ }
@@ -152,7 +152,8 @@ export async function server_upsert_sessions(sessionsData) {
152
152
  if (updates.length > 0) {
153
153
  for (const sessionData of updates) {
154
154
  const updateResult = await server_upsert_session(sessionData);
155
- if (updateResult.status === EnumWebResponse.SUCCESS && updateResult.data) {
155
+ if (updateResult.status === EnumWebResponse.SUCCESS &&
156
+ updateResult.data) {
156
157
  results.push(updateResult.data);
157
158
  }
158
159
  else {
@@ -233,7 +234,8 @@ export async function server_upsert_connectivity_batch(connectivityDataArray) {
233
234
  if (updates.length > 0) {
234
235
  for (const connectivityData of updates) {
235
236
  const updateResult = await server_upsert_connectivity(connectivityData);
236
- if (updateResult.status === EnumWebResponse.SUCCESS && updateResult.data) {
237
+ if (updateResult.status === EnumWebResponse.SUCCESS &&
238
+ updateResult.data) {
237
239
  results.push(updateResult.data);
238
240
  }
239
241
  else {
@@ -266,8 +268,10 @@ export async function server_get_session_with_connectivity_and_events(sessionId,
266
268
  if (herdId) {
267
269
  // Use provided herd ID directly
268
270
  const sessionsResult = await server_get_sessions_by_herd_id(herdId);
269
- if (sessionsResult.status === EnumWebResponse.SUCCESS && sessionsResult.data) {
270
- sessionWithCoords = sessionsResult.data.find((s) => s.id === sessionId) || null;
271
+ if (sessionsResult.status === EnumWebResponse.SUCCESS &&
272
+ sessionsResult.data) {
273
+ sessionWithCoords =
274
+ sessionsResult.data.find((s) => s.id === sessionId) || null;
271
275
  }
272
276
  }
273
277
  else {
@@ -308,16 +312,22 @@ export async function server_get_session_with_connectivity_and_events(sessionId,
308
312
  };
309
313
  }
310
314
  const sessionsResult = await server_get_sessions_by_herd_id(device.herd_id);
311
- if (sessionsResult.status === EnumWebResponse.SUCCESS && sessionsResult.data) {
312
- sessionWithCoords = sessionsResult.data.find((s) => s.id === sessionId) || null;
315
+ if (sessionsResult.status === EnumWebResponse.SUCCESS &&
316
+ sessionsResult.data) {
317
+ sessionWithCoords =
318
+ sessionsResult.data.find((s) => s.id === sessionId) || null;
313
319
  }
314
320
  }
315
321
  const [connectivityResult, eventsResult] = await Promise.all([
316
322
  server_get_connectivity_by_session_id(sessionId),
317
323
  server_get_events_by_session_id(sessionId),
318
324
  ]);
319
- const connectivity = connectivityResult.status === EnumWebResponse.SUCCESS ? connectivityResult.data || [] : [];
320
- const events = eventsResult.status === EnumWebResponse.SUCCESS ? eventsResult.data || [] : [];
325
+ const connectivity = connectivityResult.status === EnumWebResponse.SUCCESS
326
+ ? connectivityResult.data || []
327
+ : [];
328
+ const events = eventsResult.status === EnumWebResponse.SUCCESS
329
+ ? eventsResult.data || []
330
+ : [];
321
331
  return IWebResponse.success({
322
332
  session: sessionWithCoords,
323
333
  connectivity,
@@ -333,8 +343,10 @@ export async function server_get_session_with_connectivity_and_events_with_tags(
333
343
  // Use provided herd ID directly
334
344
  actualHerdId = herdId;
335
345
  const sessionsResult = await server_get_sessions_by_herd_id(herdId);
336
- if (sessionsResult.status === EnumWebResponse.SUCCESS && sessionsResult.data) {
337
- sessionWithCoords = sessionsResult.data.find((s) => s.id === sessionId) || null;
346
+ if (sessionsResult.status === EnumWebResponse.SUCCESS &&
347
+ sessionsResult.data) {
348
+ sessionWithCoords =
349
+ sessionsResult.data.find((s) => s.id === sessionId) || null;
338
350
  }
339
351
  }
340
352
  else {
@@ -378,8 +390,10 @@ export async function server_get_session_with_connectivity_and_events_with_tags(
378
390
  }
379
391
  actualHerdId = device.herd_id;
380
392
  const sessionsResult = await server_get_sessions_by_herd_id(device.herd_id);
381
- if (sessionsResult.status === EnumWebResponse.SUCCESS && sessionsResult.data) {
382
- sessionWithCoords = sessionsResult.data.find((s) => s.id === sessionId) || null;
393
+ if (sessionsResult.status === EnumWebResponse.SUCCESS &&
394
+ sessionsResult.data) {
395
+ sessionWithCoords =
396
+ sessionsResult.data.find((s) => s.id === sessionId) || null;
383
397
  }
384
398
  }
385
399
  const [connectivityResult, eventsWithTagsResult, totalEventsResult] = await Promise.all([
@@ -387,9 +401,15 @@ export async function server_get_session_with_connectivity_and_events_with_tags(
387
401
  server_get_events_and_tags_by_session_id(sessionId, limit, offset),
388
402
  server_get_total_events_for_session(sessionId),
389
403
  ]);
390
- const connectivity = connectivityResult.status === EnumWebResponse.SUCCESS ? connectivityResult.data || [] : [];
391
- const eventsWithTags = eventsWithTagsResult.status === EnumWebResponse.SUCCESS ? eventsWithTagsResult.data || [] : [];
392
- const totalEvents = totalEventsResult.status === EnumWebResponse.SUCCESS ? totalEventsResult.data || 0 : 0;
404
+ const connectivity = connectivityResult.status === EnumWebResponse.SUCCESS
405
+ ? connectivityResult.data || []
406
+ : [];
407
+ const eventsWithTags = eventsWithTagsResult.status === EnumWebResponse.SUCCESS
408
+ ? eventsWithTagsResult.data || []
409
+ : [];
410
+ const totalEvents = totalEventsResult.status === EnumWebResponse.SUCCESS
411
+ ? totalEventsResult.data || 0
412
+ : 0;
393
413
  return IWebResponse.success({
394
414
  session: sessionWithCoords,
395
415
  connectivity,
@@ -8,7 +8,7 @@ interface ConnectionStatus {
8
8
  retryCount: number;
9
9
  reconnect: () => void;
10
10
  }
11
- export declare function useSupabase(): SupabaseClient<Database, "public", {
11
+ export declare function useSupabase(): SupabaseClient<Database, "public", "public", {
12
12
  Tables: {
13
13
  actions: {
14
14
  Row: {
@@ -397,6 +397,39 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
397
397
  referencedColumns: ["id"];
398
398
  }];
399
399
  };
400
+ providers: {
401
+ Row: {
402
+ created_at: string;
403
+ herd_id: number;
404
+ id: number;
405
+ key: string | null;
406
+ source: string;
407
+ type: string;
408
+ };
409
+ Insert: {
410
+ created_at?: string;
411
+ herd_id: number;
412
+ id?: number;
413
+ key?: string | null;
414
+ source: string;
415
+ type: string;
416
+ };
417
+ Update: {
418
+ created_at?: string;
419
+ herd_id?: number;
420
+ id?: number;
421
+ key?: string | null;
422
+ source?: string;
423
+ type?: string;
424
+ };
425
+ Relationships: [{
426
+ foreignKeyName: "providers_herd_id_fkey";
427
+ columns: ["herd_id"];
428
+ isOneToOne: false;
429
+ referencedRelation: "herds";
430
+ referencedColumns: ["id"];
431
+ }];
432
+ };
400
433
  sessions: {
401
434
  Row: {
402
435
  altitude_average: number;
@@ -408,9 +441,9 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
408
441
  earthranger_url: string | null;
409
442
  id: number;
410
443
  inserted_at: string;
411
- locations: unknown;
444
+ locations: unknown | null;
412
445
  software_version: string;
413
- timestamp_end: string;
446
+ timestamp_end: string | null;
414
447
  timestamp_start: string;
415
448
  velocity_average: number;
416
449
  velocity_max: number;
@@ -426,9 +459,9 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
426
459
  earthranger_url?: string | null;
427
460
  id?: number;
428
461
  inserted_at?: string;
429
- locations: unknown;
462
+ locations?: unknown | null;
430
463
  software_version: string;
431
- timestamp_end: string;
464
+ timestamp_end?: string | null;
432
465
  timestamp_start: string;
433
466
  velocity_average: number;
434
467
  velocity_max: number;
@@ -444,9 +477,9 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
444
477
  earthranger_url?: string | null;
445
478
  id?: number;
446
479
  inserted_at?: string;
447
- locations?: unknown;
480
+ locations?: unknown | null;
448
481
  software_version?: string;
449
- timestamp_end?: string;
482
+ timestamp_end?: string | null;
450
483
  timestamp_start?: string;
451
484
  velocity_average?: number;
452
485
  velocity_max?: number;
@@ -829,7 +862,7 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
829
862
  device_type: "trail_camera" | "drone_fixed_wing" | "drone_quad" | "gps_tracker" | "sentry_tower" | "smart_buoy" | "radio_mesh_base_station" | "radio_mesh_repeater" | "unknown";
830
863
  media_type: "image" | "video" | "audio" | "text";
831
864
  plan_type: "mission" | "fence" | "rally" | "markov";
832
- role: "admin" | "viewer" | "editor";
865
+ role: "admin" | "viewer" | "editor" | "operator";
833
866
  tag_observation_type: "manual" | "auto";
834
867
  user_status: "ONLINE" | "OFFLINE";
835
868
  };
@@ -970,6 +1003,8 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
970
1003
  actions: Database["public"]["Tables"]["actions"]["Row"][] | null;
971
1004
  };
972
1005
  };
1006
+ }, {
1007
+ PostgrestVersion: "13.0.5";
973
1008
  }>;
974
1009
  export declare function useConnectionStatus(): ConnectionStatus;
975
1010
  export interface ScoutRefreshProviderProps {
@@ -1,4 +1,4 @@
1
- export declare const useAppDispatch: <AppDispatch extends import("redux").Dispatch<import("redux").AnyAction> = import("redux").Dispatch<import("redux").AnyAction>>() => AppDispatch;
1
+ export declare const useAppDispatch: import("react-redux").UseDispatch<import("redux").Dispatch<import("redux").UnknownAction>>;
2
2
  export declare const useHerdModulesLoadingState: () => any;
3
3
  export declare const useIsHerdModulesLoading: () => boolean;
4
4
  export declare const useIsHerdModulesLoaded: () => boolean;