@adventurelabs/scout-core 1.4.48 → 1.4.50

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 +1,4 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
1
3
  export declare function isEmailValidForLogin(email: string, approved_domains?: string[]): boolean;
4
+ export declare function isCurrentUserPlatformSuperadmin(client: SupabaseClient<Database>): Promise<boolean>;
@@ -6,3 +6,11 @@ export function isEmailValidForLogin(email, approved_domains = APPROVED_DOMAINS)
6
6
  function isEmailFromApprovedDomain(email, approved_domains = APPROVED_DOMAINS) {
7
7
  return approved_domains.filter((domain) => email.endsWith(domain)).length > 0;
8
8
  }
9
+ export async function isCurrentUserPlatformSuperadmin(client) {
10
+ const { data, error } = await client.rpc("is_platform_superadmin_current_user");
11
+ if (error) {
12
+ console.warn("[scout-core auth] Failed to check platform superadmin status:", error.message);
13
+ return false;
14
+ }
15
+ return data === true;
16
+ }
@@ -8,5 +8,7 @@ export { useScoutRealtimeSessions } from "./useScoutRealtimeSessions";
8
8
  export { useScoutRealtimePlans } from "./useScoutRealtimePlans";
9
9
  export { useScoutRealtimePins } from "./useScoutRealtimePins";
10
10
  export { useScoutRealtimeParts } from "./useScoutRealtimeParts";
11
+ export { useScoutRealtimeAnalysisJobs } from "./useScoutRealtimeAnalysisJobs";
12
+ export { useScoutRealtimeAnalysisTasks } from "./useScoutRealtimeAnalysisTasks";
11
13
  export { useInfiniteSessionsByHerd, useInfiniteSessionsByDevice, useInfiniteEventsByHerd, useInfiniteEventsByDevice, useInfiniteArtifactsByHerd, useInfiniteArtifactsByDevice, useInfiniteFeedByHerd, useInfiniteFeedByDevice, useInfiniteAnalysisJobs, useInfiniteAnalysisTasks, useIntersectionObserver, type UseInfiniteScrollOptions, type UseAnalysisJobsInfiniteOptions, type UseAnalysisTasksInfiniteOptions, } from "./useInfiniteQuery";
12
14
  export { useLoadingPerformance, useSessionSummariesByHerd, useHasSessionSummaries, } from "../store/hooks";
@@ -8,6 +8,8 @@ export { useScoutRealtimeSessions } from "./useScoutRealtimeSessions";
8
8
  export { useScoutRealtimePlans } from "./useScoutRealtimePlans";
9
9
  export { useScoutRealtimePins } from "./useScoutRealtimePins";
10
10
  export { useScoutRealtimeParts } from "./useScoutRealtimeParts";
11
+ export { useScoutRealtimeAnalysisJobs } from "./useScoutRealtimeAnalysisJobs";
12
+ export { useScoutRealtimeAnalysisTasks } from "./useScoutRealtimeAnalysisTasks";
11
13
  // RTK Query infinite scroll hooks
12
14
  export { useInfiniteSessionsByHerd, useInfiniteSessionsByDevice, useInfiniteEventsByHerd, useInfiniteEventsByDevice, useInfiniteArtifactsByHerd, useInfiniteArtifactsByDevice, useInfiniteFeedByHerd, useInfiniteFeedByDevice, useInfiniteAnalysisJobs, useInfiniteAnalysisTasks, useIntersectionObserver, } from "./useInfiniteQuery";
13
15
  // Session summaries and performance hooks
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { IAnalysisJob } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimeAnalysisJobs(scoutSupabase: SupabaseClient<Database>): [RealtimeData<IAnalysisJob> | null, () => void];
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { useEffect, useRef, useCallback, useState } from "react";
3
+ import { EnumRealtimeOperation } from "../types/realtime";
4
+ export function useScoutRealtimeAnalysisJobs(scoutSupabase) {
5
+ const channels = useRef([]);
6
+ const [latestJobUpdate, setLatestJobUpdate] = useState(null);
7
+ const handleAnalysisJobBroadcast = useCallback((payload) => {
8
+ const { payload: data } = payload;
9
+ const jobData = data.record || data.old_record;
10
+ if (!jobData) {
11
+ return;
12
+ }
13
+ let operation;
14
+ switch (data.operation) {
15
+ case "INSERT":
16
+ operation = EnumRealtimeOperation.INSERT;
17
+ break;
18
+ case "UPDATE":
19
+ operation = EnumRealtimeOperation.UPDATE;
20
+ break;
21
+ case "DELETE":
22
+ operation = EnumRealtimeOperation.DELETE;
23
+ break;
24
+ default:
25
+ return;
26
+ }
27
+ console.log(`[scout-core realtime] ANALYSIS_JOB ${data.operation} received for job ${jobData.id}:`, JSON.stringify(jobData));
28
+ const realtimeData = {
29
+ data: jobData,
30
+ operation,
31
+ };
32
+ setLatestJobUpdate(realtimeData);
33
+ }, []);
34
+ const clearLatestUpdate = useCallback(() => {
35
+ setLatestJobUpdate(null);
36
+ }, []);
37
+ useEffect(() => {
38
+ if (!scoutSupabase)
39
+ return;
40
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
41
+ channels.current = [];
42
+ clearLatestUpdate();
43
+ const channel = scoutSupabase
44
+ .channel("analysis_jobs_changes", { config: { private: true } })
45
+ .on("broadcast", { event: "*" }, handleAnalysisJobBroadcast)
46
+ .subscribe((status) => {
47
+ if (status === "SUBSCRIBED") {
48
+ console.log("[ANALYSIS_JOBS] ✅ Connected to analysis jobs broadcasts");
49
+ }
50
+ else if (status === "CHANNEL_ERROR") {
51
+ console.warn("[ANALYSIS_JOBS] 🟡 Failed to connect to analysis jobs broadcasts");
52
+ }
53
+ });
54
+ channels.current.push(channel);
55
+ return () => {
56
+ channels.current.forEach((ch) => scoutSupabase.removeChannel(ch));
57
+ channels.current = [];
58
+ };
59
+ }, [scoutSupabase, handleAnalysisJobBroadcast, clearLatestUpdate]);
60
+ return [latestJobUpdate, clearLatestUpdate];
61
+ }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { IAnalysisTask } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimeAnalysisTasks(scoutSupabase: SupabaseClient<Database>): [RealtimeData<IAnalysisTask> | null, () => void];
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { useEffect, useRef, useCallback, useState } from "react";
3
+ import { EnumRealtimeOperation } from "../types/realtime";
4
+ export function useScoutRealtimeAnalysisTasks(scoutSupabase) {
5
+ const channels = useRef([]);
6
+ const [latestTaskUpdate, setLatestTaskUpdate] = useState(null);
7
+ const handleAnalysisTaskBroadcast = useCallback((payload) => {
8
+ const { payload: data } = payload;
9
+ const taskData = data.record || data.old_record;
10
+ if (!taskData) {
11
+ return;
12
+ }
13
+ let operation;
14
+ switch (data.operation) {
15
+ case "INSERT":
16
+ operation = EnumRealtimeOperation.INSERT;
17
+ break;
18
+ case "UPDATE":
19
+ operation = EnumRealtimeOperation.UPDATE;
20
+ break;
21
+ case "DELETE":
22
+ operation = EnumRealtimeOperation.DELETE;
23
+ break;
24
+ default:
25
+ return;
26
+ }
27
+ console.log(`[scout-core realtime] ANALYSIS_TASK ${data.operation} received for task ${taskData.id} (job ${taskData.job_id}):`, JSON.stringify(taskData));
28
+ const realtimeData = {
29
+ data: taskData,
30
+ operation,
31
+ };
32
+ setLatestTaskUpdate(realtimeData);
33
+ }, []);
34
+ const clearLatestUpdate = useCallback(() => {
35
+ setLatestTaskUpdate(null);
36
+ }, []);
37
+ useEffect(() => {
38
+ if (!scoutSupabase)
39
+ return;
40
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
41
+ channels.current = [];
42
+ clearLatestUpdate();
43
+ const channel = scoutSupabase
44
+ .channel("analysis_tasks_changes", { config: { private: true } })
45
+ .on("broadcast", { event: "*" }, handleAnalysisTaskBroadcast)
46
+ .subscribe((status) => {
47
+ if (status === "SUBSCRIBED") {
48
+ console.log("[ANALYSIS_TASKS] ✅ Connected to analysis tasks broadcasts");
49
+ }
50
+ else if (status === "CHANNEL_ERROR") {
51
+ console.warn("[ANALYSIS_TASKS] 🟡 Failed to connect to analysis tasks broadcasts");
52
+ }
53
+ });
54
+ channels.current.push(channel);
55
+ return () => {
56
+ channels.current.forEach((ch) => scoutSupabase.removeChannel(ch));
57
+ channels.current = [];
58
+ };
59
+ }, [scoutSupabase, handleAnalysisTaskBroadcast, clearLatestUpdate]);
60
+ return [latestTaskUpdate, clearLatestUpdate];
61
+ }
@@ -117,38 +117,42 @@ export function useScoutRefresh(options = {}) {
117
117
  // Background fetch fresh data without blocking
118
118
  (async () => {
119
119
  try {
120
- const backgroundStartTime = Date.now();
121
- const [backgroundHerdModulesResult, backgroundUserResult] = await Promise.all([
122
- (async () => {
123
- const start = Date.now();
124
- const result = await server_load_herd_modules();
125
- const totalDuration = Date.now() - start;
126
- const serverDuration = result.server_processing_time_ms || totalDuration;
127
- const clientOverhead = totalDuration - serverDuration;
128
- console.log(`[useScoutRefresh] Background API timing breakdown:`);
129
- console.log(` - Server processing: ${serverDuration}ms`);
130
- console.log(` - Client overhead: ${clientOverhead}ms`);
131
- console.log(` - Total request: ${totalDuration}ms`);
132
- timingRefs.current.herdModulesDuration = serverDuration;
133
- dispatch(setHerdModulesApiServerProcessingDuration(serverDuration));
134
- dispatch(setHerdModulesApiTotalRequestDuration(totalDuration));
135
- return result;
136
- })(),
137
- (async () => {
138
- const start = Date.now();
139
- const { data } = await supabase.auth.getUser();
140
- const duration = Date.now() - start;
141
- timingRefs.current.userApiDuration = duration;
142
- dispatch(setUserApiDuration(duration));
143
- return { data: data.user, status: "success" };
144
- })(),
145
- ]);
146
- const backgroundDuration = Date.now() - backgroundStartTime;
120
+ const userPromise = (async () => {
121
+ const start = Date.now();
122
+ const { data } = await supabase.auth.getUser();
123
+ const duration = Date.now() - start;
124
+ timingRefs.current.userApiDuration = duration;
125
+ dispatch(setUserApiDuration(duration));
126
+ if (data.user) {
127
+ dispatch(setUser(data.user));
128
+ }
129
+ return data.user ?? null;
130
+ })();
131
+ let backgroundHerdModulesResult;
132
+ try {
133
+ const start = Date.now();
134
+ const result = await server_load_herd_modules();
135
+ const totalDuration = Date.now() - start;
136
+ const serverDuration = result.server_processing_time_ms || totalDuration;
137
+ const clientOverhead = totalDuration - serverDuration;
138
+ console.log(`[useScoutRefresh] Background API timing breakdown:`);
139
+ console.log(` - Server processing: ${serverDuration}ms`);
140
+ console.log(` - Client overhead: ${clientOverhead}ms`);
141
+ console.log(` - Total request: ${totalDuration}ms`);
142
+ timingRefs.current.herdModulesDuration = serverDuration;
143
+ dispatch(setHerdModulesApiServerProcessingDuration(serverDuration));
144
+ dispatch(setHerdModulesApiTotalRequestDuration(totalDuration));
145
+ backgroundHerdModulesResult = result;
146
+ }
147
+ catch (e) {
148
+ await userPromise.catch(() => { });
149
+ throw e;
150
+ }
151
+ const backgroundUserResult = await userPromise;
147
152
  // Validate background responses
148
153
  if (backgroundHerdModulesResult.data &&
149
154
  Array.isArray(backgroundHerdModulesResult.data) &&
150
- backgroundUserResult &&
151
- backgroundUserResult.data) {
155
+ backgroundUserResult) {
152
156
  // Update cache with fresh data
153
157
  try {
154
158
  await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs);
@@ -164,10 +168,6 @@ export function useScoutRefresh(options = {}) {
164
168
  // Update store with fresh data from background request
165
169
  console.log(`[useScoutRefresh] Updating store with background herd modules`);
166
170
  dispatch(setHerdModules(backgroundHerdModulesResult.data));
167
- if (backgroundUserResult && backgroundUserResult.data) {
168
- console.log(`[useScoutRefresh] Updating store with background user data`);
169
- dispatch(setUser(backgroundUserResult.data));
170
- }
171
171
  // Update data source to DATABASE
172
172
  dispatch(setDataSource(EnumDataSource.DATABASE));
173
173
  dispatch(setDataSourceInfo({
@@ -226,35 +226,41 @@ export function useScoutRefresh(options = {}) {
226
226
  lastQueryAtRef.current = Date.now();
227
227
  return;
228
228
  }
229
- // Step 2: Load fresh data from API
229
+ // Step 2: Load fresh data from API (user resolves independently; Redux gets user as soon as getUser returns)
230
230
  const parallelStartTime = Date.now();
231
- const [herdModulesResult, userResult] = await Promise.all([
232
- (async () => {
231
+ const userPromise = (async () => {
232
+ const start = Date.now();
233
+ const { data } = await supabase.auth.getUser();
234
+ const duration = Date.now() - start;
235
+ timingRefs.current.userApiDuration = duration;
236
+ dispatch(setUserApiDuration(duration));
237
+ if (!data.user) {
238
+ throw new Error("Invalid user response");
239
+ }
240
+ dispatch(setUser(data.user));
241
+ return data.user;
242
+ })();
243
+ let herdModulesResult;
244
+ try {
245
+ herdModulesResult = await (async () => {
233
246
  const start = Date.now();
234
247
  const result = await server_load_herd_modules();
235
248
  const totalDuration = Date.now() - start;
236
249
  const serverDuration = result.server_processing_time_ms || totalDuration;
237
250
  return { result, totalDuration, serverDuration, start };
238
- })(),
239
- (async () => {
240
- const start = Date.now();
241
- const { data } = await supabase.auth.getUser();
242
- const duration = Date.now() - start;
243
- return {
244
- result: { data: data.user, status: "success" },
245
- duration,
246
- start,
247
- };
248
- })(),
249
- ]);
251
+ })();
252
+ }
253
+ catch (e) {
254
+ await userPromise.catch(() => { });
255
+ throw e;
256
+ }
257
+ await userPromise;
250
258
  const parallelDuration = Date.now() - parallelStartTime;
251
- console.log(`[useScoutRefresh] Parallel API requests completed in ${parallelDuration}ms`);
259
+ console.log(`[useScoutRefresh] API requests completed in ${parallelDuration}ms`);
252
260
  // Extract results and timing
253
261
  const herdModulesResponse = herdModulesResult.result;
254
- const res_new_user = userResult.result;
255
262
  const herdModulesServerDuration = herdModulesResult.serverDuration;
256
263
  const herdModulesTotalDuration = herdModulesResult.totalDuration;
257
- const userApiDuration = userResult.duration;
258
264
  const clientOverhead = herdModulesTotalDuration - herdModulesServerDuration;
259
265
  console.log(`[useScoutRefresh] Fresh API timing breakdown:`);
260
266
  console.log(` - Server processing: ${herdModulesServerDuration}ms`);
@@ -262,20 +268,15 @@ export function useScoutRefresh(options = {}) {
262
268
  console.log(` - Total request: ${herdModulesTotalDuration}ms`);
263
269
  // Store timing values
264
270
  timingRefs.current.herdModulesDuration = herdModulesServerDuration;
265
- timingRefs.current.userApiDuration = userApiDuration;
266
271
  // Dispatch timing actions
267
272
  dispatch(setHerdModulesApiServerProcessingDuration(herdModulesServerDuration));
268
273
  dispatch(setHerdModulesApiTotalRequestDuration(herdModulesTotalDuration));
269
- dispatch(setUserApiDuration(userApiDuration));
270
274
  // Validate API responses
271
275
  const validationStartTime = Date.now();
272
276
  if (!herdModulesResponse.data ||
273
277
  !Array.isArray(herdModulesResponse.data)) {
274
278
  throw new Error("Invalid herd modules response");
275
279
  }
276
- if (!res_new_user || !res_new_user.data) {
277
- throw new Error("Invalid user response");
278
- }
279
280
  const validationDuration = Date.now() - validationStartTime;
280
281
  console.log(`[useScoutRefresh] Data validation took: ${validationDuration}ms`);
281
282
  // Use the validated data
@@ -305,8 +306,6 @@ export function useScoutRefresh(options = {}) {
305
306
  // Update store with new data
306
307
  console.log(`[useScoutRefresh] Updating store with fresh herd modules`);
307
308
  dispatch(setHerdModules(compatible_new_herd_modules));
308
- console.log(`[useScoutRefresh] Updating store with fresh user data`);
309
- dispatch(setUser(res_new_user.data));
310
309
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
311
310
  const dataProcessingDuration = Date.now() - dataProcessingStartTime;
312
311
  timingRefs.current.dataProcessingDuration = dataProcessingDuration;
package/dist/index.d.ts CHANGED
@@ -55,6 +55,8 @@ export * from "./hooks/useScoutRealtimeSessions";
55
55
  export * from "./hooks/useScoutRealtimeParts";
56
56
  export * from "./hooks/useScoutRealtimePlans";
57
57
  export * from "./hooks/useScoutRealtimePins";
58
+ export * from "./hooks/useScoutRealtimeAnalysisJobs";
59
+ export * from "./hooks/useScoutRealtimeAnalysisTasks";
58
60
  export * from "./hooks/useScoutRefresh";
59
61
  export * from "./hooks/useInfiniteQuery";
60
62
  export * from "./providers";
package/dist/index.js CHANGED
@@ -59,6 +59,8 @@ export * from "./hooks/useScoutRealtimeSessions";
59
59
  export * from "./hooks/useScoutRealtimeParts";
60
60
  export * from "./hooks/useScoutRealtimePlans";
61
61
  export * from "./hooks/useScoutRealtimePins";
62
+ export * from "./hooks/useScoutRealtimeAnalysisJobs";
63
+ export * from "./hooks/useScoutRealtimeAnalysisTasks";
62
64
  export * from "./hooks/useScoutRefresh";
63
65
  export * from "./hooks/useInfiniteQuery";
64
66
  // Providers
@@ -700,6 +700,8 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
700
700
  };
701
701
  herds: {
702
702
  Row: {
703
+ auto_delete_media_with_humans: boolean | null;
704
+ auto_delete_media_with_no_tracks: boolean | null;
703
705
  created_by: string;
704
706
  description: string;
705
707
  earthranger_domain: string | null;
@@ -714,6 +716,8 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
714
716
  video_subscriber_token: string | null;
715
717
  };
716
718
  Insert: {
719
+ auto_delete_media_with_humans?: boolean | null;
720
+ auto_delete_media_with_no_tracks?: boolean | null;
717
721
  created_by: string;
718
722
  description: string;
719
723
  earthranger_domain?: string | null;
@@ -728,6 +732,8 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
728
732
  video_subscriber_token?: string | null;
729
733
  };
730
734
  Update: {
735
+ auto_delete_media_with_humans?: boolean | null;
736
+ auto_delete_media_with_no_tracks?: boolean | null;
731
737
  created_by?: string;
732
738
  description?: string;
733
739
  earthranger_domain?: string | null;
@@ -1615,6 +1621,10 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
1615
1621
  isSetofReturn: true;
1616
1622
  };
1617
1623
  };
1624
+ auto_assign_session_post_approver: {
1625
+ Args: never;
1626
+ Returns: number;
1627
+ };
1618
1628
  check_realtime_schema_status: {
1619
1629
  Args: never;
1620
1630
  Returns: {
@@ -2620,6 +2630,10 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
2620
2630
  isSetofReturn: true;
2621
2631
  };
2622
2632
  };
2633
+ is_platform_superadmin_current_user: {
2634
+ Args: never;
2635
+ Returns: boolean;
2636
+ };
2623
2637
  load_api_keys: {
2624
2638
  Args: {
2625
2639
  id_of_device: number;
@@ -2911,6 +2925,8 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
2911
2925
  location: string | null;
2912
2926
  latitude: number | null;
2913
2927
  longitude: number | null;
2928
+ auto_delete_media_with_humans: boolean | null;
2929
+ auto_delete_media_with_no_tracks: boolean | null;
2914
2930
  };
2915
2931
  pins_pretty_location: {
2916
2932
  id: number | null;
@@ -733,6 +733,8 @@ export type Database = {
733
733
  };
734
734
  herds: {
735
735
  Row: {
736
+ auto_delete_media_with_humans: boolean | null;
737
+ auto_delete_media_with_no_tracks: boolean | null;
736
738
  created_by: string;
737
739
  description: string;
738
740
  earthranger_domain: string | null;
@@ -747,6 +749,8 @@ export type Database = {
747
749
  video_subscriber_token: string | null;
748
750
  };
749
751
  Insert: {
752
+ auto_delete_media_with_humans?: boolean | null;
753
+ auto_delete_media_with_no_tracks?: boolean | null;
750
754
  created_by: string;
751
755
  description: string;
752
756
  earthranger_domain?: string | null;
@@ -761,6 +765,8 @@ export type Database = {
761
765
  video_subscriber_token?: string | null;
762
766
  };
763
767
  Update: {
768
+ auto_delete_media_with_humans?: boolean | null;
769
+ auto_delete_media_with_no_tracks?: boolean | null;
764
770
  created_by?: string;
765
771
  description?: string;
766
772
  earthranger_domain?: string | null;
@@ -1700,6 +1706,10 @@ export type Database = {
1700
1706
  isSetofReturn: true;
1701
1707
  };
1702
1708
  };
1709
+ auto_assign_session_post_approver: {
1710
+ Args: never;
1711
+ Returns: number;
1712
+ };
1703
1713
  check_realtime_schema_status: {
1704
1714
  Args: never;
1705
1715
  Returns: {
@@ -2705,6 +2715,10 @@ export type Database = {
2705
2715
  isSetofReturn: true;
2706
2716
  };
2707
2717
  };
2718
+ is_platform_superadmin_current_user: {
2719
+ Args: never;
2720
+ Returns: boolean;
2721
+ };
2708
2722
  load_api_keys: {
2709
2723
  Args: {
2710
2724
  id_of_device: number;
@@ -2996,6 +3010,8 @@ export type Database = {
2996
3010
  location: string | null;
2997
3011
  latitude: number | null;
2998
3012
  longitude: number | null;
3013
+ auto_delete_media_with_humans: boolean | null;
3014
+ auto_delete_media_with_no_tracks: boolean | null;
2999
3015
  };
3000
3016
  pins_pretty_location: {
3001
3017
  id: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.4.48",
3
+ "version": "1.4.50",
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",