@adventurelabs/scout-core 2.0.16 → 2.0.18

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.
@@ -3,6 +3,7 @@ export * from "../types";
3
3
  export * from "../helpers/artifactMedia";
4
4
  export * from "../helpers/bounding_boxes";
5
5
  export * from "../helpers/email";
6
+ export * from "../helpers/eventMedia";
6
7
  export * from "../helpers/eventUtils";
7
8
  export { default as get_gps_center } from "../helpers/gps";
8
9
  export * from "../helpers/herd_modules_equal";
@@ -6,6 +6,7 @@ export * from "../types";
6
6
  export * from "../helpers/artifactMedia";
7
7
  export * from "../helpers/bounding_boxes";
8
8
  export * from "../helpers/email";
9
+ export * from "../helpers/eventMedia";
9
10
  export * from "../helpers/eventUtils";
10
11
  export { default as get_gps_center } from "../helpers/gps";
11
12
  export * from "../helpers/herd_modules_equal";
@@ -1,4 +1,4 @@
1
- import { isNonEmptyStorageFilePath } from "./storagePath";
1
+ import { isNonEmptyStorageFilePath, signedUrlForPath } from "./storagePath";
2
2
  /** Collect unique non-empty storage paths used for artifact media signing. */
3
3
  export function collectArtifactStoragePaths(artifacts) {
4
4
  const seen = new Set();
@@ -17,9 +17,6 @@ export function collectArtifactStoragePaths(artifacts) {
17
17
  }
18
18
  return paths;
19
19
  }
20
- function signedUrlForPath(path, urlMap) {
21
- return isNonEmptyStorageFilePath(path) ? urlMap.get(path) ?? null : null;
22
- }
23
20
  /** Attach signed URLs for original, thumbnail, and proxy storage paths. */
24
21
  export function withArtifactMediaUrls(artifact, urlMap) {
25
22
  return {
@@ -0,0 +1,12 @@
1
+ import { ISignedMediaUrls } from "../types/db";
2
+ /** Events arrive as table rows and as the event_and_tags composite, so match on shape. */
3
+ export type IEventMediaPaths = {
4
+ file_path?: string | null;
5
+ thumbnail_file_path?: string | null;
6
+ proxy_file_path?: string | null;
7
+ media_url?: string | null;
8
+ };
9
+ /** Collect unique non-empty storage paths used for event media signing. */
10
+ export declare function collectEventStoragePaths(events: Iterable<IEventMediaPaths | null | undefined>): string[];
11
+ /** Attach signed URLs for original, thumbnail, and proxy storage paths. */
12
+ export declare function withEventMediaUrls<T extends IEventMediaPaths>(event: T, urlMap: Map<string, string | null | undefined>): T & ISignedMediaUrls;
@@ -0,0 +1,29 @@
1
+ import { isNonEmptyStorageFilePath, signedUrlForPath } from "./storagePath";
2
+ /** Collect unique non-empty storage paths used for event media signing. */
3
+ export function collectEventStoragePaths(events) {
4
+ const seen = new Set();
5
+ const paths = [];
6
+ for (const event of events) {
7
+ for (const path of [
8
+ event?.file_path,
9
+ event?.thumbnail_file_path,
10
+ event?.proxy_file_path,
11
+ ]) {
12
+ if (isNonEmptyStorageFilePath(path) && !seen.has(path)) {
13
+ seen.add(path);
14
+ paths.push(path);
15
+ }
16
+ }
17
+ }
18
+ return paths;
19
+ }
20
+ /** Attach signed URLs for original, thumbnail, and proxy storage paths. */
21
+ export function withEventMediaUrls(event, urlMap) {
22
+ return {
23
+ ...event,
24
+ // Events predating file_path carry an absolute media_url with nothing to sign.
25
+ media_url: signedUrlForPath(event.file_path, urlMap) ?? event.media_url ?? null,
26
+ thumbnail_url: signedUrlForPath(event.thumbnail_file_path, urlMap),
27
+ proxy_url: signedUrlForPath(event.proxy_file_path, urlMap),
28
+ };
29
+ }
@@ -103,14 +103,19 @@ export declare function server_update_event_lifecycle(event_id: number, lifecycl
103
103
  origin_roll: number | null;
104
104
  processing_blocked_at: string | null;
105
105
  processing_blocked_reason: string | null;
106
+ proxy_file_path: string | null;
107
+ proxy_generated_at: string | null;
106
108
  segmented_at: string | null;
107
109
  sensor_pitch: number | null;
108
110
  sensor_roll: number | null;
109
111
  sensor_yaw: number | null;
110
112
  session_id: number | null;
111
113
  tagged_at: string | null;
114
+ thumbnail_file_path: string | null;
115
+ thumbnail_generated_at: string | null;
112
116
  timestamp_observation: string;
113
117
  tracked_at: string | null;
118
+ updated_at: string;
114
119
  } | null>>;
115
120
  export declare function server_update_artifact_lifecycle(artifact_id: number, lifecycle: EntityLifecycle, options?: LifecycleUpdateOptions): Promise<import("../types").IWebResponseCompatible<{
116
121
  created_at: string;
@@ -136,7 +141,7 @@ export declare function server_update_artifact_lifecycle(artifact_id: number, li
136
141
  timestamp_observation: string | null;
137
142
  timestamp_observation_end: string;
138
143
  tracked_at: string | null;
139
- updated_at: string | null;
144
+ updated_at: string;
140
145
  } | null>>;
141
146
  export declare function server_update_user_role_per_herd_lifecycle(membership_id: number, lifecycle: EntityLifecycle, options?: LifecycleUpdateOptions): Promise<import("../types").IWebResponseCompatible<{
142
147
  herd_id: number;
@@ -1,13 +1,7 @@
1
1
  import type { SupabaseClient } from "@supabase/supabase-js";
2
- import type { IArtifact, IArtifactWithMediaUrl } from "../types/db";
2
+ import type { IArtifact, IArtifactWithMediaUrl, ISignedMediaUrls } from "../types/db";
3
3
  import type { Database } from "../types/supabase";
4
- type EventWithStoragePath = {
5
- file_path?: string | null;
6
- media_url?: string | null;
7
- };
4
+ import { type IEventMediaPaths } from "./eventMedia";
8
5
  export declare function createSignedUrlMap(client: SupabaseClient<Database>, paths: Iterable<string | null | undefined>): Promise<Map<string, string | null>>;
9
- export declare function addEventMediaUrls<T extends EventWithStoragePath>(client: SupabaseClient<Database>, events: T[]): Promise<Array<T & {
10
- media_url: string | null;
11
- }>>;
6
+ export declare function addEventMediaUrls<T extends IEventMediaPaths>(client: SupabaseClient<Database>, events: T[]): Promise<Array<T & ISignedMediaUrls>>;
12
7
  export declare function addArtifactMediaUrls(client: SupabaseClient<Database>, artifacts: IArtifact[]): Promise<IArtifactWithMediaUrl[]>;
13
- export {};
@@ -1,4 +1,5 @@
1
1
  import { collectArtifactStoragePaths, withArtifactMediaUrls, } from "./artifactMedia";
2
+ import { collectEventStoragePaths, withEventMediaUrls, } from "./eventMedia";
2
3
  import { generateSignedUrlsBatchWithClient } from "./storage_internal";
3
4
  import { isNonEmptyStorageFilePath } from "./storagePath";
4
5
  export async function createSignedUrlMap(client, paths) {
@@ -7,13 +8,8 @@ export async function createSignedUrlMap(client, paths) {
7
8
  return new Map(uniquePaths.map((path, index) => [path, urls[index]]));
8
9
  }
9
10
  export async function addEventMediaUrls(client, events) {
10
- const urlMap = await createSignedUrlMap(client, events.map((event) => event.file_path));
11
- return events.map((event) => ({
12
- ...event,
13
- media_url: isNonEmptyStorageFilePath(event.file_path)
14
- ? urlMap.get(event.file_path) ?? event.media_url ?? null
15
- : event.media_url ?? null,
16
- }));
11
+ const urlMap = await createSignedUrlMap(client, collectEventStoragePaths(events));
12
+ return events.map((event) => withEventMediaUrls(event, urlMap));
17
13
  }
18
14
  export async function addArtifactMediaUrls(client, artifacts) {
19
15
  const urlMap = await createSignedUrlMap(client, collectArtifactStoragePaths(artifacts));
@@ -1,5 +1,7 @@
1
1
  /** True when we can sign storage: non-null and not blank after trim. */
2
2
  export declare function isNonEmptyStorageFilePath(path: string | null | undefined): path is string;
3
+ /** Signed URL for a storage path, or null when the path is blank or went unsigned. */
4
+ export declare function signedUrlForPath(path: string | null | undefined, urlMap: Map<string, string | null | undefined>): string | null;
3
5
  /** Assumes DB `file_path` is always `bucket/object/...` */
4
6
  export declare function parseStorageFilePath(filePath: string): {
5
7
  bucket: string;
@@ -2,6 +2,10 @@
2
2
  export function isNonEmptyStorageFilePath(path) {
3
3
  return path != null && path.trim() !== "";
4
4
  }
5
+ /** Signed URL for a storage path, or null when the path is blank or went unsigned. */
6
+ export function signedUrlForPath(path, urlMap) {
7
+ return isNonEmptyStorageFilePath(path) ? urlMap.get(path) ?? null : null;
8
+ }
5
9
  /** Assumes DB `file_path` is always `bucket/object/...` */
6
10
  export function parseStorageFilePath(filePath) {
7
11
  const cleaned = filePath.trim().replace(/^\/+|\/+$/g, "");
@@ -340,6 +340,10 @@ export async function get_event_and_tags_by_event_id_query(client, event_id) {
340
340
  embedded_at: data[0].embedded_at ?? null,
341
341
  segmented_at: data[0].segmented_at ?? null,
342
342
  tagged_at: data[0].tagged_at ?? null,
343
+ thumbnail_file_path: data[0].thumbnail_file_path ?? null,
344
+ thumbnail_generated_at: data[0].thumbnail_generated_at ?? null,
345
+ proxy_file_path: data[0].proxy_file_path ?? null,
346
+ proxy_generated_at: data[0].proxy_generated_at ?? null,
343
347
  };
344
348
  const [eventWithUrl] = await addEventMediaUrls(client, [transformedData]);
345
349
  return IWebResponse.success(eventWithUrl).to_compatible();
@@ -4,6 +4,7 @@ export * from "../types";
4
4
  export * from "../helpers/artifactMedia";
5
5
  export * from "../helpers/bounding_boxes";
6
6
  export * from "../helpers/email";
7
+ export * from "../helpers/eventMedia";
7
8
  export * from "../helpers/eventUtils";
8
9
  export { default as get_gps_center } from "../helpers/gps";
9
10
  export * from "../helpers/herd_modules_equal";
@@ -6,6 +6,7 @@ export * from "../types";
6
6
  export * from "../helpers/artifactMedia";
7
7
  export * from "../helpers/bounding_boxes";
8
8
  export * from "../helpers/email";
9
+ export * from "../helpers/eventMedia";
9
10
  export * from "../helpers/eventUtils";
10
11
  export { default as get_gps_center } from "../helpers/gps";
11
12
  export * from "../helpers/herd_modules_equal";
package/dist/store/api.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
3
3
  import { addArtifactMediaUrls, addEventMediaUrls, createSignedUrlMap, } from "../helpers/media_urls";
4
4
  import { collectArtifactStoragePaths, withArtifactMediaUrls, } from "../helpers/artifactMedia";
5
- import { isNonEmptyStorageFilePath } from "../helpers/storagePath";
5
+ import { collectEventStoragePaths, withEventMediaUrls, } from "../helpers/eventMedia";
6
6
  async function signedUrlMapForFeedRows(supabase, rows) {
7
7
  return createSignedUrlMap(supabase, rows.flatMap((row) => [
8
- row.event_data?.file_path,
8
+ ...collectEventStoragePaths([row.event_data]),
9
9
  ...(row.artifact_data
10
10
  ? collectArtifactStoragePaths([row.artifact_data])
11
11
  : []),
@@ -434,12 +434,7 @@ export const scoutApi = createApi({
434
434
  const items = resultRows.map((row) => ({
435
435
  ...row,
436
436
  event_data: row.event_data
437
- ? {
438
- ...row.event_data,
439
- media_url: isNonEmptyStorageFilePath(row.event_data.file_path)
440
- ? urlMap.get(row.event_data.file_path) ?? null
441
- : null,
442
- }
437
+ ? withEventMediaUrls(row.event_data, urlMap)
443
438
  : null,
444
439
  artifact_data: row.artifact_data
445
440
  ? withArtifactMediaUrls(row.artifact_data, urlMap)
@@ -504,12 +499,7 @@ export const scoutApi = createApi({
504
499
  const items = resultRows.map((row) => ({
505
500
  ...row,
506
501
  event_data: row.event_data
507
- ? {
508
- ...row.event_data,
509
- media_url: isNonEmptyStorageFilePath(row.event_data.file_path)
510
- ? urlMap.get(row.event_data.file_path) ?? null
511
- : null,
512
- }
502
+ ? withEventMediaUrls(row.event_data, urlMap)
513
503
  : null,
514
504
  artifact_data: row.artifact_data
515
505
  ? withArtifactMediaUrls(row.artifact_data, urlMap)
@@ -217,14 +217,13 @@ export type BatteryCycleCountUpdate = Database["public"]["Tables"]["battery_cycl
217
217
  export type IMinimalSubjectForArtifact = Database["public"]["Functions"]["get_minimal_subjects_for_artifact"]["Returns"][number];
218
218
  export type IAnalysisJob = Database["public"]["Tables"]["analysis_jobs"]["Row"];
219
219
  export type IAnalysisTask = Database["public"]["Tables"]["analysis_tasks"]["Row"];
220
- export type IArtifactWithMediaUrl = IArtifact & {
220
+ export type ISignedMediaUrls = {
221
221
  media_url?: string | null;
222
222
  thumbnail_url?: string | null;
223
223
  proxy_url?: string | null;
224
224
  };
225
- export type IEventWithMediaUrl = IEvent & {
226
- media_url?: string | null;
227
- };
225
+ export type IArtifactWithMediaUrl = IArtifact & ISignedMediaUrls;
226
+ export type IEventWithMediaUrl = IEvent & ISignedMediaUrls;
228
227
  export type IVersionsSoftwareWithBuildUrl = IVersionsSoftware & {
229
228
  build_artifact_url?: string | null;
230
229
  };
@@ -368,9 +367,7 @@ export type IFeedItem = {
368
367
  feed_type: string | null;
369
368
  sort_ts: string | null;
370
369
  sort_id: number | null;
371
- event_data: (IEventAndTagsPrettyLocation & {
372
- media_url?: string | null;
373
- }) | null;
370
+ event_data: (IEventAndTagsPrettyLocation & ISignedMediaUrls) | null;
374
371
  artifact_data: IArtifactWithMediaUrl | null;
375
372
  };
376
373
  export type ISessionWithCoordinates = Database["public"]["CompositeTypes"]["session_with_coordinates"];
@@ -269,7 +269,7 @@ export type Database = {
269
269
  timestamp_observation: string | null;
270
270
  timestamp_observation_end: string;
271
271
  tracked_at: string | null;
272
- updated_at: string | null;
272
+ updated_at: string;
273
273
  };
274
274
  Insert: {
275
275
  created_at?: string;
@@ -295,7 +295,7 @@ export type Database = {
295
295
  timestamp_observation?: string | null;
296
296
  timestamp_observation_end?: string;
297
297
  tracked_at?: string | null;
298
- updated_at?: string | null;
298
+ updated_at?: string;
299
299
  };
300
300
  Update: {
301
301
  created_at?: string;
@@ -321,7 +321,7 @@ export type Database = {
321
321
  timestamp_observation?: string | null;
322
322
  timestamp_observation_end?: string;
323
323
  tracked_at?: string | null;
324
- updated_at?: string | null;
324
+ updated_at?: string;
325
325
  };
326
326
  Relationships: [
327
327
  {
@@ -1504,14 +1504,19 @@ export type Database = {
1504
1504
  origin_roll: number | null;
1505
1505
  processing_blocked_at: string | null;
1506
1506
  processing_blocked_reason: string | null;
1507
+ proxy_file_path: string | null;
1508
+ proxy_generated_at: string | null;
1507
1509
  segmented_at: string | null;
1508
1510
  sensor_pitch: number | null;
1509
1511
  sensor_roll: number | null;
1510
1512
  sensor_yaw: number | null;
1511
1513
  session_id: number | null;
1512
1514
  tagged_at: string | null;
1515
+ thumbnail_file_path: string | null;
1516
+ thumbnail_generated_at: string | null;
1513
1517
  timestamp_observation: string;
1514
1518
  tracked_at: string | null;
1519
+ updated_at: string;
1515
1520
  };
1516
1521
  Insert: {
1517
1522
  altitude?: number;
@@ -1538,14 +1543,19 @@ export type Database = {
1538
1543
  origin_roll?: number | null;
1539
1544
  processing_blocked_at?: string | null;
1540
1545
  processing_blocked_reason?: string | null;
1546
+ proxy_file_path?: string | null;
1547
+ proxy_generated_at?: string | null;
1541
1548
  segmented_at?: string | null;
1542
1549
  sensor_pitch?: number | null;
1543
1550
  sensor_roll?: number | null;
1544
1551
  sensor_yaw?: number | null;
1545
1552
  session_id?: number | null;
1546
1553
  tagged_at?: string | null;
1554
+ thumbnail_file_path?: string | null;
1555
+ thumbnail_generated_at?: string | null;
1547
1556
  timestamp_observation?: string;
1548
1557
  tracked_at?: string | null;
1558
+ updated_at?: string;
1549
1559
  };
1550
1560
  Update: {
1551
1561
  altitude?: number;
@@ -1572,14 +1582,19 @@ export type Database = {
1572
1582
  origin_roll?: number | null;
1573
1583
  processing_blocked_at?: string | null;
1574
1584
  processing_blocked_reason?: string | null;
1585
+ proxy_file_path?: string | null;
1586
+ proxy_generated_at?: string | null;
1575
1587
  segmented_at?: string | null;
1576
1588
  sensor_pitch?: number | null;
1577
1589
  sensor_roll?: number | null;
1578
1590
  sensor_yaw?: number | null;
1579
1591
  session_id?: number | null;
1580
1592
  tagged_at?: string | null;
1593
+ thumbnail_file_path?: string | null;
1594
+ thumbnail_generated_at?: string | null;
1581
1595
  timestamp_observation?: string;
1582
1596
  tracked_at?: string | null;
1597
+ updated_at?: string;
1583
1598
  };
1584
1599
  Relationships: [
1585
1600
  {
@@ -5405,7 +5420,7 @@ export type Database = {
5405
5420
  timestamp_observation: string | null;
5406
5421
  timestamp_observation_end: string;
5407
5422
  tracked_at: string | null;
5408
- updated_at: string | null;
5423
+ updated_at: string;
5409
5424
  }[];
5410
5425
  SetofOptions: {
5411
5426
  from: "*";
@@ -5443,7 +5458,7 @@ export type Database = {
5443
5458
  timestamp_observation: string | null;
5444
5459
  timestamp_observation_end: string;
5445
5460
  tracked_at: string | null;
5446
- updated_at: string | null;
5461
+ updated_at: string;
5447
5462
  }[];
5448
5463
  SetofOptions: {
5449
5464
  from: "*";
@@ -5482,7 +5497,7 @@ export type Database = {
5482
5497
  timestamp_observation: string | null;
5483
5498
  timestamp_observation_end: string;
5484
5499
  tracked_at: string | null;
5485
- updated_at: string | null;
5500
+ updated_at: string;
5486
5501
  }[];
5487
5502
  SetofOptions: {
5488
5503
  from: "*";
@@ -5521,7 +5536,7 @@ export type Database = {
5521
5536
  timestamp_observation: string | null;
5522
5537
  timestamp_observation_end: string;
5523
5538
  tracked_at: string | null;
5524
- updated_at: string | null;
5539
+ updated_at: string;
5525
5540
  }[];
5526
5541
  SetofOptions: {
5527
5542
  from: "*";
@@ -5561,7 +5576,7 @@ export type Database = {
5561
5576
  timestamp_observation: string | null;
5562
5577
  timestamp_observation_end: string;
5563
5578
  tracked_at: string | null;
5564
- updated_at: string | null;
5579
+ updated_at: string;
5565
5580
  }[];
5566
5581
  SetofOptions: {
5567
5582
  from: "*";
@@ -5601,7 +5616,7 @@ export type Database = {
5601
5616
  timestamp_observation: string | null;
5602
5617
  timestamp_observation_end: string;
5603
5618
  tracked_at: string | null;
5604
- updated_at: string | null;
5619
+ updated_at: string;
5605
5620
  }[];
5606
5621
  SetofOptions: {
5607
5622
  from: "*";
@@ -5642,7 +5657,7 @@ export type Database = {
5642
5657
  timestamp_observation: string | null;
5643
5658
  timestamp_observation_end: string;
5644
5659
  tracked_at: string | null;
5645
- updated_at: string | null;
5660
+ updated_at: string;
5646
5661
  }[];
5647
5662
  SetofOptions: {
5648
5663
  from: "*";
@@ -5682,7 +5697,7 @@ export type Database = {
5682
5697
  timestamp_observation: string | null;
5683
5698
  timestamp_observation_end: string;
5684
5699
  tracked_at: string | null;
5685
- updated_at: string | null;
5700
+ updated_at: string;
5686
5701
  }[];
5687
5702
  SetofOptions: {
5688
5703
  from: "*";
@@ -5723,7 +5738,7 @@ export type Database = {
5723
5738
  timestamp_observation: string | null;
5724
5739
  timestamp_observation_end: string;
5725
5740
  tracked_at: string | null;
5726
- updated_at: string | null;
5741
+ updated_at: string;
5727
5742
  }[];
5728
5743
  SetofOptions: {
5729
5744
  from: "*";
@@ -7015,6 +7030,10 @@ export type Database = {
7015
7030
  embedded_at: string | null;
7016
7031
  segmented_at: string | null;
7017
7032
  tagged_at: string | null;
7033
+ thumbnail_file_path: string | null;
7034
+ thumbnail_generated_at: string | null;
7035
+ proxy_file_path: string | null;
7036
+ proxy_generated_at: string | null;
7018
7037
  };
7019
7038
  event_plus_tags: {
7020
7039
  id: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "exports": {
6
6
  "./client": {