@adventurelabs/scout-core 2.0.16 → 2.0.17

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,12 +103,16 @@ 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;
114
118
  } | null>>;
@@ -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"];
@@ -1504,12 +1504,16 @@ 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;
1515
1519
  };
@@ -1538,12 +1542,16 @@ export type Database = {
1538
1542
  origin_roll?: number | null;
1539
1543
  processing_blocked_at?: string | null;
1540
1544
  processing_blocked_reason?: string | null;
1545
+ proxy_file_path?: string | null;
1546
+ proxy_generated_at?: string | null;
1541
1547
  segmented_at?: string | null;
1542
1548
  sensor_pitch?: number | null;
1543
1549
  sensor_roll?: number | null;
1544
1550
  sensor_yaw?: number | null;
1545
1551
  session_id?: number | null;
1546
1552
  tagged_at?: string | null;
1553
+ thumbnail_file_path?: string | null;
1554
+ thumbnail_generated_at?: string | null;
1547
1555
  timestamp_observation?: string;
1548
1556
  tracked_at?: string | null;
1549
1557
  };
@@ -1572,12 +1580,16 @@ export type Database = {
1572
1580
  origin_roll?: number | null;
1573
1581
  processing_blocked_at?: string | null;
1574
1582
  processing_blocked_reason?: string | null;
1583
+ proxy_file_path?: string | null;
1584
+ proxy_generated_at?: string | null;
1575
1585
  segmented_at?: string | null;
1576
1586
  sensor_pitch?: number | null;
1577
1587
  sensor_roll?: number | null;
1578
1588
  sensor_yaw?: number | null;
1579
1589
  session_id?: number | null;
1580
1590
  tagged_at?: string | null;
1591
+ thumbnail_file_path?: string | null;
1592
+ thumbnail_generated_at?: string | null;
1581
1593
  timestamp_observation?: string;
1582
1594
  tracked_at?: string | null;
1583
1595
  };
@@ -7015,6 +7027,10 @@ export type Database = {
7015
7027
  embedded_at: string | null;
7016
7028
  segmented_at: string | null;
7017
7029
  tagged_at: string | null;
7030
+ thumbnail_file_path: string | null;
7031
+ thumbnail_generated_at: string | null;
7032
+ proxy_file_path: string | null;
7033
+ proxy_generated_at: string | null;
7018
7034
  };
7019
7035
  event_plus_tags: {
7020
7036
  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.17",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "exports": {
6
6
  "./client": {