@adventurelabs/scout-core 1.0.82 → 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;
@@ -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
+ }
@@ -397,6 +397,39 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
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;
@@ -21,6 +21,7 @@ export type IUserRolePerHerd = Database["public"]["Tables"]["users_roles_per_her
21
21
  export type IHerd = Database["public"]["Tables"]["herds"]["Row"];
22
22
  export type ISession = Database["public"]["Tables"]["sessions"]["Row"];
23
23
  export type IConnectivity = Database["public"]["Tables"]["connectivity"]["Row"];
24
+ export type IProvider = Database["public"]["Tables"]["providers"]["Row"];
24
25
  export type IEventWithTags = Database["public"]["CompositeTypes"]["event_with_tags"] & {
25
26
  earthranger_url: string | null;
26
27
  file_path: string | null;
@@ -1,5 +1,5 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
- import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
2
+ import { IDevice, IEventWithTags, IHerd, IPlan, ILayer, IProvider, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
3
3
  import { EnumWebResponse } from "./requests";
4
4
  export declare enum EnumHerdModulesLoadingState {
5
5
  NOT_LOADING = "NOT_LOADING",
@@ -21,7 +21,8 @@ export declare class HerdModule {
21
21
  labels: string[];
22
22
  plans: IPlan[];
23
23
  layers: ILayer[];
24
- constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[], layers?: ILayer[]);
24
+ providers: IProvider[];
25
+ constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[], layers?: ILayer[], providers?: IProvider[]);
25
26
  to_serializable(): IHerdModule;
26
27
  static from_herd(herd: IHerd, client: SupabaseClient): Promise<HerdModule>;
27
28
  }
@@ -39,6 +40,7 @@ export interface IHerdModule {
39
40
  zones: IZoneWithActions[];
40
41
  sessions: ISessionWithCoordinates[];
41
42
  layers: ILayer[];
43
+ providers: IProvider[];
42
44
  }
43
45
  export interface IHerdModulesResponse {
44
46
  data: IHerdModule[];
@@ -4,6 +4,7 @@ import { server_get_total_events_by_herd } from "../helpers/events";
4
4
  import { EnumSessionsVisibility } from "./events";
5
5
  import { server_get_plans_by_herd } from "../helpers/plans";
6
6
  import { server_get_layers_by_herd } from "../helpers/layers";
7
+ import { server_get_providers_by_herd } from "../helpers/providers";
7
8
  import { server_get_events_and_tags_for_devices_batch } from "../helpers/tags";
8
9
  import { server_get_users_with_herd_access } from "../helpers/users";
9
10
  import { EnumWebResponse } from "./requests";
@@ -18,7 +19,7 @@ export var EnumHerdModulesLoadingState;
18
19
  EnumHerdModulesLoadingState["UNSUCCESSFULLY_LOADED"] = "UNSUCCESSFULLY_LOADED";
19
20
  })(EnumHerdModulesLoadingState || (EnumHerdModulesLoadingState = {}));
20
21
  export class HerdModule {
21
- constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = []) {
22
+ constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = [], layers = [], providers = []) {
22
23
  this.user_roles = null;
23
24
  this.events_page_index = 0;
24
25
  this.total_events = 0;
@@ -26,6 +27,7 @@ export class HerdModule {
26
27
  this.labels = [];
27
28
  this.plans = [];
28
29
  this.layers = [];
30
+ this.providers = [];
29
31
  this.herd = herd;
30
32
  this.devices = devices;
31
33
  this.events = events;
@@ -39,6 +41,7 @@ export class HerdModule {
39
41
  this.zones = zones;
40
42
  this.sessions = sessions;
41
43
  this.layers = layers;
44
+ this.providers = providers;
42
45
  }
43
46
  to_serializable() {
44
47
  return {
@@ -55,6 +58,7 @@ export class HerdModule {
55
58
  zones: this.zones,
56
59
  sessions: this.sessions,
57
60
  layers: this.layers,
61
+ providers: this.providers,
58
62
  };
59
63
  }
60
64
  static async from_herd(herd, client) {
@@ -95,7 +99,7 @@ export class HerdModule {
95
99
  }
96
100
  }
97
101
  // Run all remaining requests in parallel with individual error handling
98
- const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers,] = await Promise.allSettled([
102
+ const [res_zones, res_user_roles, total_event_count, res_plans, res_sessions, res_layers, res_providers,] = await Promise.allSettled([
99
103
  server_get_more_zones_and_actions_for_herd(herd.id, 0, 10).catch((error) => {
100
104
  console.warn(`[HerdModule] Failed to get zones and actions:`, error);
101
105
  return { status: EnumWebResponse.ERROR, data: null };
@@ -114,12 +118,20 @@ export class HerdModule {
114
118
  }),
115
119
  server_get_sessions_by_herd_id(herd.id).catch((error) => {
116
120
  console.warn(`[HerdModule] Failed to get sessions:`, error);
117
- return { status: EnumWebResponse.ERROR, data: [], msg: error.message };
121
+ return {
122
+ status: EnumWebResponse.ERROR,
123
+ data: [],
124
+ msg: error.message,
125
+ };
118
126
  }),
119
127
  server_get_layers_by_herd(herd.id).catch((error) => {
120
128
  console.warn(`[HerdModule] Failed to get layers:`, error);
121
129
  return { status: EnumWebResponse.ERROR, data: null };
122
130
  }),
131
+ server_get_providers_by_herd(herd.id).catch((error) => {
132
+ console.warn(`[HerdModule] Failed to get providers:`, error);
133
+ return { status: EnumWebResponse.ERROR, data: null };
134
+ }),
123
135
  ]);
124
136
  // Assign recent events to devices from batch results
125
137
  for (let i = 0; i < new_devices.length; i++) {
@@ -148,23 +160,28 @@ export class HerdModule {
148
160
  const plans = res_plans.status === "fulfilled" && res_plans.value?.data
149
161
  ? res_plans.value.data
150
162
  : [];
151
- const sessions = res_sessions.status === "fulfilled" && res_sessions.value?.data ? res_sessions.value.data : [];
163
+ const sessions = res_sessions.status === "fulfilled" && res_sessions.value?.data
164
+ ? res_sessions.value.data
165
+ : [];
152
166
  const layers = res_layers.status === "fulfilled" && res_layers.value?.data
153
167
  ? res_layers.value.data
154
168
  : [];
169
+ const providers = res_providers.status === "fulfilled" && res_providers.value?.data
170
+ ? res_providers.value.data
171
+ : [];
155
172
  // TODO: store in DB and retrieve on load?
156
173
  const newLabels = LABELS;
157
174
  const endTime = Date.now();
158
175
  const loadTime = endTime - startTime;
159
176
  console.log(`[HerdModule] Loaded herd ${herd.slug} in ${loadTime}ms (${new_devices.length} devices)`);
160
- return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers);
177
+ return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions, layers, providers);
161
178
  }
162
179
  catch (error) {
163
180
  const endTime = Date.now();
164
181
  const loadTime = endTime - startTime;
165
182
  console.error(`[HerdModule] Critical error in HerdModule.from_herd (${loadTime}ms):`, error);
166
183
  // Return a minimal but valid HerdModule instance to prevent complete failure
167
- return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], []);
184
+ return new HerdModule(herd, [], [], Date.now(), null, 0, 0, 0, [], [], [], [], [], []);
168
185
  }
169
186
  }
170
187
  }
@@ -416,6 +416,41 @@ export type Database = {
416
416
  }
417
417
  ];
418
418
  };
419
+ providers: {
420
+ Row: {
421
+ created_at: string;
422
+ herd_id: number;
423
+ id: number;
424
+ key: string | null;
425
+ source: string;
426
+ type: string;
427
+ };
428
+ Insert: {
429
+ created_at?: string;
430
+ herd_id: number;
431
+ id?: number;
432
+ key?: string | null;
433
+ source: string;
434
+ type: string;
435
+ };
436
+ Update: {
437
+ created_at?: string;
438
+ herd_id?: number;
439
+ id?: number;
440
+ key?: string | null;
441
+ source?: string;
442
+ type?: string;
443
+ };
444
+ Relationships: [
445
+ {
446
+ foreignKeyName: "providers_herd_id_fkey";
447
+ columns: ["herd_id"];
448
+ isOneToOne: false;
449
+ referencedRelation: "herds";
450
+ referencedColumns: ["id"];
451
+ }
452
+ ];
453
+ };
419
454
  sessions: {
420
455
  Row: {
421
456
  altitude_average: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.82",
3
+ "version": "1.0.83",
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",